DOCS/troubleshooting/250825_gmail_token_slack_id_migration.md
Claude-51124 fe487247d5 fix: Gmail 토큰 스키마 불일치 문제 문서 정정
- slack_user_id 컬럼 존재하지 않음 명시
- 실제 테이블 구조 반영 (user_id, equipped_to)
- JOIN을 통한 올바른 조회 방법 제시
- 미완성 상태 명확히 표시
2025-09-18 18:11:38 +09:00

6.4 KiB

Gmail Token 시스템 Slack ID 직접 사용 마이그레이션

개요

2025년 8월 25일, Gmail 토큰 조회 시스템을 UUID 변환 방식에서 Slack ID 직접 사용 방식으로 마이그레이션했습니다.

문제 상황

기존 시스템의 문제점

  1. UUID 불일치 문제

    • auth-server: uuid.uuid4() (랜덤 UUID) 사용
    • skill-email/rb8001: uuid.uuid5(namespace, slack_id) (네임스페이스 기반 UUID) 사용
    • 결과: 동일한 사용자에 대해 서로 다른 UUID가 생성되어 토큰 조회 실패
  2. 데이터베이스 스키마 변경

    • 기존: token_data JSON 컬럼에 모든 토큰 정보 저장
    • 변경: 개별 컬럼으로 분리 (access_token, refresh_token, oauth_config, scopes, expiry)
    • 새 컬럼 추가: slack_id (Slack 사용자 ID 직접 저장) [2025-09-18 정정]
    • 실제 스키마: user_id(UUID), equipped_to(Gmail 계정)

해결 방안

1. Slack ID 직접 사용

UUID 변환을 완전히 제거하고 Slack ID를 직접 사용하도록 변경

2. 변경된 파일

/home/heejae/skill-email/services/db_credentials_provider.py

제거된 코드:

import uuid

def __init__(self, connection_string: str):
    # ...
    self.uuid_namespace = uuid.NAMESPACE_DNS  # 제거됨
    
def _convert_to_uuid(self, user_id: str) -> str:  # 메서드 전체 제거
    """Slack user_id를 UUID로 변환"""
    # ...

변경된 쿼리:

# 이전
user_uuid = self._convert_to_uuid(user_id)
cur.execute("""
    SELECT ... FROM gmail_tokens
    WHERE user_id = %s AND is_equipped = true
""", (user_uuid,))

# 현재 (잘못된 코드 - 2025-09-18)
cur.execute("""
    SELECT ... FROM gmail_tokens
    WHERE slack_user_id = %s AND is_equipped = true  # 존재하지 않는 컬럼
""", (user_id,))

# 올바른 방법: JOIN을 통한 조회
cur.execute("""
    SELECT gt.* FROM gmail_token gt
    JOIN user u ON gt.user_id = u.id
    WHERE u.oauth_id = %s AND gt.is_equipped = true
""", (slack_id,))

/home/heejae/skill-email/services/gmail_service.py

추가된 자동 토큰 갱신:

def _get_gmail_service(self, user_id: str):
    # ...
    # 토큰이 만료되었거나 곧 만료될 경우 자동 refresh
    if creds and creds.expired and creds.refresh_token:
        logger.info(f"Refreshing expired token for user: {user_id}")
        from google.auth.transport.requests import Request
        creds.refresh(Request())
        
        # 갱신된 토큰 저장
        if hasattr(self.creds_provider, 'save_credentials'):
            self.creds_provider.save_credentials(user_id, creds)
            logger.info(f"Refreshed token saved for user: {user_id}")

401 에러 시 재시도 로직:

except HttpError as e:
    if e.resp.status == 401:
        logger.info(f"Got 401 error, refreshing token for user: {user_id}")
        # 캐시 삭제 후 재시도
        if user_id in self._service_cache:
            del self._service_cache[user_id]
        # 토큰 refresh 포함하여 다시 시도

3. 데이터베이스 스키마

gmail_token 테이블 구조 (실제 - 2025-09-18 확인):

CREATE TABLE gmail_token (  -- 테이블명 주의: gmail_token (s 없음)
    id UUID PRIMARY KEY,
    user_id UUID NOT NULL,  -- user 테이블 FK
    token_data JSONB,  -- access_token, refresh_token 포함
    oauth_config JSONB,
    scopes JSONB,
    metadata JSONB,
    expiry TIMESTAMP,
    is_equipped BOOLEAN DEFAULT false,
    equipped_to VARCHAR(50),  -- Gmail 계정 이메일
    token_type VARCHAR DEFAULT 'Bearer',
    expires_at DOUBLE PRECISION,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 올바른 조회 방법: user 테이블과 JOIN
-- user.oauth_id에 Slack ID가 저장됨

영향 받는 서비스

  1. skill-email (포트 8501)

    • db_credentials_provider.py 수정
    • gmail_service.py 자동 refresh 로직 추가
  2. rb8001 (포트 8001)

    • dm_skill.py의 일일 요약 기능
    • Gmail 이메일 조회 시 skill-email API 사용
  3. auth-server (포트 8088)

    • 변경 없음 (이미 slack_id 저장 중)
  4. robeing-monitor (포트 9024)

    • 이미 수정 완료 (새 컬럼 구조 읽기)

테스트 및 검증

1. 이메일 조회 테스트

# 개별 사용자 이메일 조회
curl -X GET "http://localhost:8501/messages?user_id=U091UNVE41M&limit=5&query=category:primary"

2. 일일 요약 DM 테스트

# Cron API 호출로 전체 사용자에게 DM 전송
curl -X POST "http://localhost:8001/api/cron/daily-summary" \
  -H "Authorization: Bearer cron-secret-2024" \
  -H "Content-Type: application/json"

3. 토큰 자동 갱신 확인

# skill-email 로그에서 자동 refresh 확인
docker logs skill-email | grep "Refreshing"

주요 개선 사항 (실제로는 미완성 - 2025-09-18)

  1. UUID 불일치 문제 해결 (미해결)

    • 모든 서비스가 동일한 식별자(slack_id) 사용
    • UUID 변환 과정 제거로 오류 가능성 감소
    • 실제: 코드는 존재하지 않는 slack_user_id 컬럼 사용 중
  2. 자동 토큰 갱신

    • 만료된 토큰 자동 감지 및 갱신
    • 401 에러 시 자동 재시도
    • 갱신된 토큰 DB 자동 저장
  3. 코드 단순화

    • UUID 변환 로직 제거
    • 직관적인 slack_id 사용
    • 디버깅 용이성 향상

배포 절차

# 1. skill-email 재빌드 및 재시작
cd /home/heejae/skill-email
docker compose down && docker compose up -d --build

# 2. 로그 확인
docker logs skill-email --tail 50

# 3. 헬스체크
curl http://localhost:8501/health

트러블슈팅

SSL SYSCALL error: EOF detected

  • 원인: PostgreSQL 연결 풀 타임아웃
  • 해결: 재시도 시 정상 작동 (연결 풀 자동 재생성)

토큰 조회 실패

  • 확인사항:
    1. slack_id 컬럼에 올바른 Slack 사용자 ID 저장 여부
    2. is_equipped = true 설정 여부
    3. SSH 터널 연결 상태 (포트 5433)

자동 갱신 실패

  • 확인사항:
    1. refresh_token 존재 여부
    2. Google OAuth 클라이언트 ID/Secret 설정
    3. 네트워크 연결 상태

참고 사항

  • SSH 터널: localhost:5433 → 124.55.18.179:5432
  • 데이터베이스: main_db
  • 환경 변수: /home/heejae/skill-email/.env
  • Docker 네트워크: network_mode: host