운영 중인 서비스는 끊임없이 진화해야 한다. 다이제스트 품질 개선부터 워크플로우 정비까지, 하루 만에 진행한 안정화 작업을 기록한다.
배경
Contents Hub는 매일 아침/저녁으로 AI 다이제스트를 이메일로 발송하는 서비스다. 17개 구독 소스에서 1800개 이상의 콘텐츠를 수집하고 있지만, 운영하면서 몇 가지 문제가 보이기 시작했다:
- 다이제스트 형식 불일치 - 대화체와 구조화된 분석이 혼재
- Notebook LM Sources 누락 - AI가 필수 섹션을 자주 누락
- 배포 실수 - Git commit 없이 배포하는 사고 발생
- 코드베이스 방치 - Untracked 파일 7개 누적
- 쿠키 만료 - Longblack 크롤러 동작 중단
하나씩 정리해보자.
1. 다이제스트 품질 개선
문제: 형식 불일치와 누락
최근 5개 다이제스트를 분석해보니 두 가지 스타일이 혼재되어 있었다. A형은 대화체 중심, B형은 구조화된 분석 중심. 독자 입장에서는 일관성이 없어 혼란스러웠을 것이다.
더 심각한 건 Notebook LM Sources 섹션 누락이었다. 프롬프트에 "필수", "절대 생략 금지"라고 명시했는데도 AI가 계속 누락했다. 이 섹션은 AI/ML 관련 URL 10개를 제공해서 Notebook LM에 바로 붙여넣을 수 있게 하는 핵심 기능인데 말이다.
해결: 표준화 + Fallback
1) 6단계 표준 형식 확립
1. SUBJECT (이메일 제목, 40자 이내)
2. 한 줄 요약
3. 에디터 노트 (2-4문장, 억지 연결 금지)
4. 오늘의 픽 (심층 분석 + So What? + 알아보기)
5. 더 보기 (카테고리별 그룹핑)
6. Notebook LM Sources (AI/ML 콘텐츠 URL 10개)
톤은 대화체("슬랙에서 동료가 던져주는 느낌"), 구조는 체계적 분석. A와 B의 장점만 취했다.
2) 후처리 Fallback 구현
AI가 Notebook LM Sources를 생성하지 못하면? 키워드 기반 자동 선별로 대응한다:
ai_keywords = ["ai", "ml", "llm", "agent", "claude", "gpt", ...]
candidate_urls = [content.url for content in contents
if any(kw in (content.title + content.content).lower()
for kw in ai_keywords)]
# 플랫폼별 최소 1개/최대 3개, 전체 5-10개
프롬프트 강화 + 후처리 fallback = 100% 보장.
2. 워크플로우 개선
문제: 배포 전 커밋 누락
v1.0.18 배포 직후 깨달았다. Git commit 없이 배포했다. 코드는 운영에 올라갔지만 Git 히스토리에는 없는 상태. 롤백도 불가능. 다행히 나중에 commit을 만들어 push했지만, 아찔한 순간이었다.
더 문제는 로컬 서버가 실행 중인 상태로 배포한 것이다. 로컬과 운영 서버가 동시에 스케줄러를 돌리면? 다이제스트 메일이 중복 발송된다. 실제로 몇 번 겪었다.
해결: 명확한 순서 + 체크리스트
CLAUDE.md에 워크플로우를 명시했다:
# ⚠️ 개발 → 커밋 → 배포 → 원격 테스트 순서 필수
1. 로컬 개발 + 테스트 (lint + pytest)
2. Git commit & push # 배포 전 필수!
3. 로컬 서버 중지 # 중복 다이제스트 방지
4. 운영 배포 (deploy.sh)
5. 원격 테스트 (health check)
배포 체크리스트도 추가했다:
- 로컬 테스트 통과
- Git commit & push 완료
- 로컬 서버 중지 확인
- 원격 배포 필요 여부 확인 (Claude에게 문의)
- 배포 후 원격 테스트
다음부터는 이 실수를 반복하지 않을 것이다.
3. 코드베이스 정리
Untracked 파일 7개가 방치되어 있었다. git status를 돌릴 때마다 신경 쓰였지만 미뤄뒀던 것들이다.
커밋한 것 (가치 있는 유틸리티):
crawl_longblack_urls.py- URL 일괄 크롤 스크립트test_new_prompt.py- 프롬프트 테스트 유틸리티notebook-sources/- 프로덕션 HTML 페이지
삭제한 것 (임시/중복 파일):
test_new_prompt_result.md- 테스트 결과물docs/prompt.txt- 엉뚱한 UI 메모docs/blog-*.md- my-sparks에 이미 존재
결과: git status 깔끔, 가치 있는 코드는 Git 히스토리에 보존.
4. 운영 이슈 대응: Longblack 쿠키 갱신
아침에 이메일이 왔다: "[Contents Hub] Longblack Cookie Expired"
Longblack은 유료 구독 플랫폼이라 쿠키 기반 크롤을 한다. 쿠키가 만료되면 크롤이 멈춘다. 즉시 대응했다:
- 로컬에서
longblack_login.py실행 → 브라우저 로그인 → 쿠키 저장 - 쿠키 파일을 운영 서버로 복사 (
scp) - Docker 컨테이너 재시작 → Health check 확인
다음 크롤링(10:00 KST)부터 정상 수집 재개. 현재 Longblack 콘텐츠 128개 누적 중.
교훈
1. 운영은 끊임없는 개선이다
처음 만들 때는 "일단 돌아가게"가 목표였다. 하지만 실제로 돌리다 보면 품질, 안정성, 유지보수성 문제가 보인다. 이번 작업처럼 주기적으로 정비해야 한다.
2. AI는 100% 신뢰할 수 없다
"필수"라고 프롬프트에 명시해도 AI는 누락할 수 있다. 중요한 기능은 반드시 fallback 메커니즘을 두자. 프롬프트 강화 + 후처리 검증 = 안정성.
3. 체크리스트는 실수를 막는다
"이번엔 안 까먹을 거야"는 착각이다. 배포 전 체크리스트를 만들어두면, 다음에는 Claude가 대신 확인해준다. 사람은 깜빡하지만 프로세스는 깜빡하지 않는다.
4. 작은 정리가 쌓이면 큰 부채가 된다
Untracked 파일 7개. "나중에 정리하지 뭐" 하고 미뤘다가 몇 주 방치했다. 오늘 30분 투자해서 깔끔하게 정리했더니 git status 볼 때마다 기분이 좋다. 기술 부채는 이자가 붙는다.
다음 단계
- Snippet 자연 축적 모니터링 - 나머지 플랫폼(RSS, YouTube, Web, X) 크롤 시 자동 생성 확인
- YouTube Data API 연동 - RSS 404 장기화 대비
- Gemini 쿼터 모니터링 - 상용 키 쿼터 소진 시 Slack 알림
- 보안 스캔 자동화 - pre-commit hook (bandit/semgrep)
운영 안정화는 끝이 없다. 하지만 오늘 하루 만에 4가지 개선을 완료했으니, 나쁘지 않다. 🚀