DOCS/troubleshooting/250824_rb8001_daily_summary_cron_failure.md
happybell80 20bc99d8e5 docs: 문서 톤 객관적으로 수정 및 해결 상태 업데이트
- 비난조 표현을 객관적 서술로 변경
- 해결된 이슈 상태 명확히 표시
- timezone 버그 수정 완료 상태 반영
- 긴급도 표현 완화
2025-08-25 01:23:10 +09:00

16 KiB

rb8001 일일 요약 크론잡 분석

작성일: 2025-08-24

작성자: 서버 관리자 with Claude

영향 서버: 51123(Gateway) → 51124(rb8001, skill-email)


1. 문제 개요

1.1 증상

  • 발생 시간: 매일 오전 9시 (KST)
  • 영향: 사용자들에게 모닝 브리핑 전송 (이메일 요약 누락)
  • 대상 사용자:
    • 전희재 (U091UNVE41M)
    • 김종태 (U0925SXQFDK)
    • HanYong Hwang (U092F7FQ55L)

1.2 에러 메시지

Gmail token not ready for user U091UNVE41M: reauth_required
500 Internal Server Error from skill-email service

2. 시스템 아키텍처

2.1 크론잡 구성 (51123 서버)

# Gateway 컨테이너 내부 크론탭
0 9 * * * curl -X POST http://192.168.219.52:8001/api/cron/daily-summary \
  -H "Content-Type: application/json" \
  2>/dev/null || true # briefing

2.2 네트워크 플로우

[51123 Gateway 크론] 
    ↓ POST 요청
[51124 rb8001:8001]
    ↓ 데이터 수집 (병렬)
    ├→ skill-news:8505 (뉴스 검색) ✅
    ├→ skill-email:8501 (이메일 조회) ❌ 500 에러
    └→ rb10508_micro (LLM 요약) ✅
    ↓
[Slack API → 사용자 DM]

2.3 인증 체계

  • 엔드포인트: /api/cron/daily-summary
  • 인증 토큰: Bearer cron-secret-2024
  • 실행 주체: Gateway 컨테이너 (51123)

3. 원인 분석

3.1 skill-email 서비스 실패 (51124 서버) - 해결 완료

데이터베이스 연결 문제 - 해결 완료 (2025-08-25 00:12)

# skill-email 환경 설정 (수정 전)
TOKEN_PROVIDER=database
DATABASE_URL=postgresql://robeings:robeings@localhost:5433/auth_db

# 수정 후
DATABASE_URL=postgresql://robeings:robeings@localhost:5433/main_db

문제점 (해결됨):

  1. auth_db 부재: 51123 서버에 auth_db 데이터베이스 없음 → main_db로 변경 완료
  2. SSH 터널 설정: 5433 포트가 51123으로 포워딩되나 DB 자체가 없음 → main_db 연결 성공
  3. 토큰 조회 실패: DBCredentialsProvider가 연결 실패로 토큰 조회 불가 → 정상 작동

에러 체인 (해결됨)

이전: auth_db 연결 실패 → 500 에러
현재: main_db 연결 성공 → 토큰 조회 가능 (단, 만료 상태)

3.2 추가 이슈

datetime import 누락 - ⚠️ 확인 필요

# rb8001/main.py 에러
NameError: name 'datetime' is not defined
  • 상태: 코드에 datetime import 있으나 특정 조건에서 발생 가능

슬랙 워크스페이스 불일치 - 별 문제 없음

  • 현재 등록: T035VFRKCN6 (GoodGang Labs)
  • 필요한 것: T0925SXPS4D (실제로는 정상 작동 중)

4. 실행 프로세스 상세

4.1 정상 동작 시나리오

  1. 09:00:00 - Gateway 크론 트리거
  2. 09:00:01 - rb8001 /api/cron/daily-summary 호출
  3. 09:00:02 - 병렬 데이터 수집 시작
    • 뉴스: AI 키워드로 최신 뉴스 5개
    • 이메일: 사용자별 최근 이메일 5개
  4. 09:00:05 - LLM 요약 생성
  5. 09:00:07 - Slack DM 전송

4.2 현재 실패 시나리오

  1. 09:00:00 - Gateway 크론 트리거
  2. 09:00:01 - rb8001 엔드포인트 호출
  3. 09:00:02 - 데이터 수집 시작
    • 뉴스: 성공
    • 이메일: 실패 (500 에러)
  4. 09:00:03 - 부분적 요약 생성 (뉴스만)
  5. 09:00:04 - 불완전한 DM 전송

5. 이슈 현황

5.1 Gmail 토큰 자동 갱신 - 부분 해결 (2025-08-24)

Timezone 버그:

  • 파일: /home/admin/auth-server/app/api/gmail_refresh.py:132
  • 수정 완료: datetime.now()datetime.now(timezone.utc) (섹션 14 참조)
  • 상태: 로컬 개발자가 수정 완료, 배포 대기

5.2 자동 갱신 메커니즘 - 📋 개선 예정

  • 현재 상태: skill-email과 rb8001이 Gmail API 호출 시 토큰 상태 직접 확인하지 않음
  • 일반적 구현: API 호출 전 토큰 만료 확인 후 필요시 refresh_token으로 갱신
  • 현재 동작: 토큰 그대로 사용, 만료 시 에러 발생
  • 개선 방향: Gmail API 호출 전 토큰 체크 로직 추가 검토

6. 검증 방법

6.1 수동 테스트

# skill-email 직접 테스트
curl -X GET http://localhost:8501/messages?user_id=U091UNVE41M \
  -H "Authorization: Bearer test-token"

# rb8001 크론 엔드포인트 테스트
curl -X POST http://localhost:8001/api/cron/daily-summary \
  -H "Authorization: Bearer cron-secret-2024"

6.2 로그 확인

# skill-email 로그
docker logs skill-email --tail 50

# rb8001 로그
docker logs rb8001 --tail 50

# Gateway 로그 (크론 실행 확인)
docker exec robeing-gateway tail -f /var/log/cron.log

7. 모니터링 포인트

7.1 일일 체크리스트

  • 오전 9시 크론 실행 여부
  • skill-email 500 에러 발생 여부
  • Slack DM 정상 수신 여부
  • 이메일 요약 포함 여부

7.2 알람 설정 권장

# 크론 실패 시 알람
0 9 * * * curl ... || echo "Cron failed" | mail -s "Alert" admin@example.com

8. 교훈 및 개선사항

8.1 아키텍처 개선

  1. 환경 일관성: 모든 서비스가 동일한 DB 사용 (main_db)
  2. 의존성 최소화: 단일 서비스 장애가 전체 기능 마비 방지
  3. 폴백 메커니즘: 이메일 실패 시에도 기본 브리핑 전송

8.2 운영 개선

  1. 헬스체크 강화: 각 마이크로서비스 상태 모니터링
  2. 에러 핸들링: graceful degradation 구현
  3. 로그 중앙화: 분산 서비스 로그 통합 관리

8.3 문서화

  1. 시스템 다이어그램: 서비스 간 의존성 명확화
  2. 환경변수 문서: 각 서비스별 필수 설정 문서화
  3. 트러블슈팅 가이드: 일반적인 문제 해결 방법 정리

9. 관련 파일

51123 서버

  • /home/admin/robeing-gateway/entrypoint.sh - 크론 시작
  • /home/admin/robeing-gateway/Dockerfile - 크론 설치

51124 서버

  • /home/admin/ivada_project/rb8001/main.py - 크론 엔드포인트
  • /home/admin/ivada_project/rb8001/app/cron/daily_summary.py - 요약 로직
  • /home/admin/ivada_project/skill_email/.env - DB 설정
  • /home/admin/ivada_project/skill_email/services/gmail_service.py - Gmail 서비스

10. 현재 상태 (2025-08-25 00:20 업데이트)

확인 사항

  • Gateway 크론: 정상 작동
  • rb8001 서비스: 정상 작동
  • skill-news: 정상 작동
  • skill-email: DB 연결 성공, 토큰 데이터 없음 ⚠️
  • auth_db → main_db 마이그레이션: 완료
  • main_db gmail_tokens: 테이블 존재, 토큰 데이터 NULL

해결된 사항 (2025-08-25 00:12)

  • skill-email DB 연결 문제: 해결
    • .env 수정: postgresql://robeings:robeings@localhost:5433/main_db
    • auth_db → main_db 마이그레이션 완료
    • 서비스 재시작 완료

토큰 상태 (2025-08-25 시점)

  • 토큰 만료 현황:
    • 0914eagle: 만료됨
    • cdctfm: 만료됨
    • happybell80: 만료됨
  • 갱신 상태: refresh_token 존재, timezone 수정 후 갱신 가능

긴급도

  • 높음: 매일 오전 9시 사용자 영향
  • 현재 상태: DB 연결 문제는 해결, OAuth 토큰 재발급 필요

11. OAuth 토큰 상태 검증 (2025-08-25 00:30)

토큰 갱신 API 테스트

# API 호출 성공
curl -X POST "http://localhost:9000/api/gmail/refresh/1e16e9d5-59f3-54da-a661-8abeabff4230"
# 응답: 200 OK, "status": "refreshed"

# 그러나 DB 확인 시 여전히 만료 상태
SELECT username, expiry FROM gmail_tokens WHERE username='happybell80';
# expiry: 2025-08-24 16:30:10 (어제 시간으로 기록됨)

12. 토큰 자동 갱신 버그 분석 (2025-08-25 00:35)

DB 검증 결과

-- 토큰 데이터 확인
SELECT username, 
       token_data IS NOT NULL as has_token_data,
       access_token IS NOT NULL as has_access, 
       refresh_token IS NOT NULL as has_refresh,
       expiry
FROM gmail_tokens;

결과:

username has_token_data has_access has_refresh expiry
0914eagle false true true 2025-08-23 16:04:15
cdctfm false true true 2025-08-23 08:52:13
happybell80 false true true 2025-08-23 08:52:11

토큰 만료 상태

-- 만료 시간 검증
SELECT username,
       CASE WHEN expiry < NOW() THEN '만료됨' ELSE '유효함' END as status,
       AGE(NOW(), expiry) as expired_since
FROM gmail_tokens;

결과:

  • 0914eagle: 만료됨 (1일 8시간 전)
  • cdctfm: 만료됨 (1일 15시간 전)
  • happybell80: 만료됨 (1일 15시간 전)

auth-server 상태

  • 컨테이너: 정상 작동 중 (32시간 운영)
  • 헬스체크: /health 엔드포인트 정상 응답
  • 상태: healthy

분석 결과

  1. token_data 필드: 모두 NULL (JSONB 필드 미사용)
  2. access_token/refresh_token: 별도 컬럼에 존재
  3. 만료 상태: 모든 토큰 24시간 이상 만료
  4. 자동 갱신 실패: refresh_token 있으나 자동 갱신 미작동

분석 결과

  • OAuth 토큰 재발급 또는 갱신 필요
  • refresh_token 존재, 갱신 API 활용 가능
  • 프론트엔드에서 재인증 옵션 제공 중

13. 조치사항 요약

13.1 완료된 수정 (2025-08-24)

  1. auth-server timezone 수정
    • 파일: /home/admin/auth-server/app/api/gmail_refresh.py
    • 수정 내용: datetime.now()datetime.now(timezone.utc)
    • 상태: 로컬 수정 완료, 배포 대기

13.2 향후 개선 고려사항

  1. API 호출 시점 자동 갱신
    # 토큰 체크 로직 예시
    if token.expiry < datetime.now():
        token = refresh_gmail_token(user_id)
    

13.3 영향받는 서비스

  • rb8001 일일 요약 (매일 오전 9시)
  • skill-email 이메일 조회 기능
  • 프론트엔드 Gmail 아이템 기능

14. 버그 수정 완료 (2025-08-24 로컬 개발자)

14.1 수정 내용

Timezone 버그 수정 - Gmail 토큰 만료 시간이 UTC가 아닌 로컬 시간으로 저장되는 문제 해결

수정 파일 1: auth-server/app/api/gmail_refresh.py

# Line 9: timezone import 추가
from datetime import datetime, timedelta, timezone

# Line 66: 토큰 유효성 체크 시 UTC 사용
remaining = check_expiry - datetime.now(timezone.utc).timestamp()

# Line 131-132: 토큰 갱신 시 UTC 기준으로 만료 시간 계산
new_expires_at = datetime.now(timezone.utc).timestamp() + expires_in
new_expiry = datetime.now(timezone.utc) + timedelta(seconds=expires_in)

# Line 247: 토큰 상태 확인 시 UTC 사용
remaining_seconds = check_expiry - datetime.now(timezone.utc).timestamp()

수정 파일 2: auth-server/app/providers/gmail_passport.py

# Line 14: timezone import 추가
from datetime import datetime, timezone

# Line 194: OAuth 콜백 처리 시 UTC 사용
datetime.now(timezone.utc).timestamp() + token_data_raw.get('expires_in', 3600)

# Line 199-200: created_at, updated_at UTC 사용
datetime.now(timezone.utc),
datetime.now(timezone.utc)

# Line 283, 306: 패스포트 활성화/비활성화 시 UTC 사용
""", user_uuid, datetime.now(timezone.utc))

14.2 수정 효과

  • 토큰 만료 시간이 정확히 UTC로 저장됨
  • 토큰 갱신 API가 올바른 시간 기준으로 작동
  • 자동 갱신 시 만료 판단이 정확해짐

14.3 다음 단계 (서버 관리자)

  1. auth-server 재배포
  2. 기존 만료된 토큰 수동 갱신 또는 재인증
  3. 자동 갱신 크론잡 설정 (선택사항)

15. 수정 후 테스트 결과 (2025-08-25 00:50)

15.1 테스트 실행

# 토큰 갱신 API 호출
curl -X POST "http://localhost:9000/api/gmail/refresh/1e16e9d5-59f3-54da-a661-8abeabff4230"

# 응답
{
    "status": "valid",
    "user_id": "1e16e9d5-59f3-54da-a661-8abeabff4230",
    "email": "goeun2dc@gmail.com",
    "expires_in": 2416,
    "access_token": "ya29.A0AS3H6N..."
}

15.2 DB 검증

-- UTC 기준 토큰 상태 확인
SELECT username, 
       expiry AT TIME ZONE 'UTC' as expiry_utc,
       CASE WHEN expiry AT TIME ZONE 'UTC' > NOW() AT TIME ZONE 'UTC' 
            THEN '유효✅' ELSE '만료❌' END as status
FROM gmail_tokens;

결과:

username expiry_utc status
happybell80 2025-08-25 01:30:10 유효
0914eagle 2025-08-24 01:04:15 만료
cdctfm 2025-08-23 17:52:13 만료

15.3 수정 효과 확인

  • 수정 전: expiry가 2025-08-24 16:30 (어제)로 저장
  • 수정 후: expiry가 2025-08-25 01:30 (정확한 UTC)로 저장
  • 결과: 토큰이 "유효"로 정상 인식됨

15.4 결론

Timezone 버그 수정 성공

  • UTC 시간대가 올바르게 적용됨
  • 토큰 갱신 API가 정상 작동
  • 주의: 자동 갱신 메커니즘은 여전히 없음 (수동 갱신 필요)

15.5 수동 갱신 실행 (2025-08-25 00:55)

  • 0914eagle, cdctfm OAuth client 정보 수정 후 수동 갱신 완료
  • 모든 사용자 토큰 유효 상태로 변경 (1시간)
  • 현재 수동 갱신으로 운영 중
  • 향후 자동 갱신 구현 시 안정성 향상 예상

16. 자동 갱신 메커니즘 부재 상세 분석 (2025-08-24 로컬 개발자)

16.1 코드 분석 결과

섹션 5.2의 내용을 코드로 확인

skill-email 현재 구현

# services/gmail_service.py (Line 76-100)
def _get_gmail_service(self, user_id: str):
    # 토큰을 DB에서 가져옴
    creds = self.creds_provider.get_credentials(user_id)
    
    # 현재: 토큰 직접 사용
    service = build("gmail", "v1", credentials=creds)
    return Ok(service)

rb8001 현재 구현

# app/skills/email_integration.py (Line 213-226)
async def process_email_request(self, message, user_id, channel):
    # Gmail 장착 상태 확인
    is_equipped = await self.check_gmail_equipped(user_id)
    
    # skill-email API 호출
    response = await client.post(f"{self.skill_email_url}/process", ...)

16.2 토큰 자동 갱신 추가 방법

# Google OAuth 표준 방식 예시
from google.auth.transport.requests import Request

def get_gmail_service(user_id):
    creds = get_credentials(user_id)
    
    # 토큰 만료 체크 및 자동 갱신
    if creds and creds.expired and creds.refresh_token:
        try:
            creds.refresh(Request())
            save_credentials(user_id, creds)
        except Exception as e:
            # 갱신 실패 시 재인증 필요
            return None
    
    return build('gmail', 'v1', credentials=creds)

16.3 현재 플로우

1. 사용자 요청 → rb8001
2. rb8001 → skill-email API 호출
3. skill-email → DB에서 토큰 조회
4. skill-email → Gmail API 호출 (만료된 토큰 그대로)
5. Gmail API → 401 Unauthorized 에러
6. skill-email → 500 Internal Server Error
7. rb8001 → 사용자에게 에러 메시지

16.4 향후 개선 방향

  1. skill-email/services/gmail_service.py:

    • _get_gmail_service() 메서드에 토큰 만료 체크 추가 고려
    • auth-server의 /api/gmail/refresh/{user_id} 활용
    • Google OAuth refresh 직접 구현 옵션
  2. skill-email/services/db_credentials_provider.py:

    • get_credentials() 시 expiry 확인 로직 추가 가능
    • refresh_credentials() 메서드 구현 검토
  3. rb8001/app/skills/email_integration.py:

    • skill-email 호출 전 토큰 상태 사전 확인 옵션
    • 필요시 갱신 API 호출 후 진행

16.5 임시 해결책 (서버 관리자)

# 크론잡 추가 - 매 30분마다 모든 토큰 갱신
*/30 * * * * for user_id in $(psql -t -c "SELECT user_id FROM gmail_tokens"); do \
  curl -X POST "http://localhost:9000/api/gmail/refresh/$user_id"; \
done

16.6 권장 사항

  • API 호출 시점 자동 갱신: Gmail API 호출 직전 토큰 체크 및 갱신 구현 시 안정성 향상
  • 장점: 크론잡 불필요, 토큰 최신 상태 유지
  • 구현 예정 위치: skill-email 서비스

문서 끝