docs: 콜드메일 플로우 분석 및 3가지 문제 문서화
- 251016_grpc_uvloop_blocking_error: gRPC + uvloop 리소스 경합 문제 - 251016_naverworks_briefing_system_uuid_error: 시스템 사용자 UUID 검증 오류 - 251014_claude_coldmail_filter_tokenization_issue: 파인티처 메일 누락 사례 추가 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
0adf06aa41
commit
3fb2deff22
@ -220,3 +220,34 @@ normal centroid: 80개 샘플 평균, 차원=384
|
||||
- 단순 centroid 방식은 75% 정확도 한계
|
||||
- 하이브리드 접근(임베딩 1차 → LLM 2차)이 더 효율적
|
||||
- Phase 1 임베딩은 보조 역할, Phase 2 LLM이 최종 판단
|
||||
|
||||
---
|
||||
|
||||
## 실제 운영 사례 (2025-10-16)
|
||||
|
||||
### 파인티처 투자 제안 메일 누락
|
||||
|
||||
**발생 시각**: 09:05:07.096
|
||||
**메일 정보**:
|
||||
- 제목: '투자검토요청 관련하여 회사소개서 첨부드립니다_파인티처'
|
||||
- 발신: petermin@fineteacher.com
|
||||
- 수신: 2025-10-15 09:44 (24시간 검색 범위 내)
|
||||
|
||||
**필터링 결과**:
|
||||
```
|
||||
Stage 1 (Embedding): REJECT
|
||||
coldmail: 0.2817, normal: 0.3435 (역전)
|
||||
```
|
||||
|
||||
**문제점**:
|
||||
- 명백한 투자 제안 메일이지만 임베딩 점수 역전으로 Stage 1 탈락
|
||||
- Stage 2 (LLM) 도달 실패로 최종 0건 검출
|
||||
- Slack 전송 없음
|
||||
|
||||
**원인**:
|
||||
- Centroid 샘플이 실제 coldmail 패턴 반영 부족
|
||||
- "투자검토요청", "회사소개서" 같은 명시적 키워드도 낮은 점수
|
||||
|
||||
**영향**:
|
||||
- 실제 IR 제안 누락으로 비즈니스 기회 손실 가능
|
||||
- 하이브리드 필터의 Stage 1 임계값 조정 필요성 확인
|
||||
|
||||
95
troubleshooting/251016_grpc_uvloop_blocking_error.md
Normal file
95
troubleshooting/251016_grpc_uvloop_blocking_error.md
Normal file
@ -0,0 +1,95 @@
|
||||
# gRPC + uvloop BlockingIOError 리소스 경합
|
||||
|
||||
**날짜**: 2025-10-16
|
||||
**작성자**: Claude
|
||||
**관련 파일**: `rb8001/main.py`, `rb8001/app/llm/emotion_classifier.py`
|
||||
|
||||
---
|
||||
|
||||
## 문제 상황
|
||||
|
||||
### 발생 시점
|
||||
- NaverWorks Daily Briefing 실행 시 (매일 09:00)
|
||||
- 감정 분석 LLM 호출 중 발생 (09:00:03.240)
|
||||
|
||||
### 에러 로그
|
||||
```
|
||||
{"time":"2025-10-16 09:00:03,240","level":"ERROR","module":"asyncio"}
|
||||
Exception in callback functools.partial(<bound method PollerCompletionQueue._handle_events of <grpc._cython.cygrpc.PollerCompletionQueue object>>)
|
||||
File "grpc/_cython/_cygrpc/aio/completion_queue.pyx.pxi", line 147, in grpc._cython.cygrpc.PollerCompletionQueue._handle_events
|
||||
BlockingIOError: [Errno 11] Resource temporarily unavailable
|
||||
```
|
||||
|
||||
### 영향
|
||||
- 작업 실패 없음 (NaverWorks Briefing 정상 완료)
|
||||
- 로그 노이즈 발생으로 실제 오류 추적 방해
|
||||
|
||||
---
|
||||
|
||||
## 원인 분석
|
||||
|
||||
### 기술적 원인
|
||||
- **uvloop**: rb8001 main.py에서 사용 중인 고성능 event loop
|
||||
- **gRPC**: emotion_classifier.py에서 Vertex AI (Gemini) 호출 시 사용
|
||||
- **리소스 경합**: uvloop의 epoll과 gRPC의 polling 메커니즘 충돌
|
||||
|
||||
### 발생 조건
|
||||
1. uvloop 활성화 상태에서 gRPC 비동기 호출
|
||||
2. 동시 다발적 LLM 요청 (감정 분석 + 요약 생성)
|
||||
3. gRPC PollerCompletionQueue의 이벤트 처리 중 일시적 리소스 부족
|
||||
|
||||
---
|
||||
|
||||
## 해결 방안
|
||||
|
||||
### 1. gRPC 이벤트 루프 로깅 억제 (권장)
|
||||
|
||||
**위치**: rb8001/main.py:1-10
|
||||
|
||||
**현재**:
|
||||
```python
|
||||
import uvloop
|
||||
uvloop.install()
|
||||
```
|
||||
|
||||
**변경**:
|
||||
```python
|
||||
import uvloop
|
||||
import logging
|
||||
uvloop.install()
|
||||
logging.getLogger("grpc").setLevel(logging.CRITICAL)
|
||||
```
|
||||
|
||||
### 2. asyncio 기본 루프 사용 (성능 하락)
|
||||
|
||||
**위치**: rb8001/main.py:1-10
|
||||
|
||||
**변경**: uvloop.install() 제거, asyncio 기본 루프 사용
|
||||
|
||||
### 3. gRPC 채널 재사용 설정
|
||||
|
||||
**위치**: rb8001/app/llm/emotion_classifier.py
|
||||
|
||||
**확인 필요**: gRPC 채널 풀링 설정 확인
|
||||
|
||||
---
|
||||
|
||||
## 구현 완료
|
||||
|
||||
미구현 (로그 노이즈만 발생, 기능 영향 없음)
|
||||
|
||||
---
|
||||
|
||||
## 교훈
|
||||
|
||||
### uvloop + gRPC 조합 주의
|
||||
- uvloop은 asyncio보다 빠르지만 gRPC와 호환성 이슈 존재
|
||||
- 교훈: 성능 라이브러리 도입 시 의존성 충돌 사전 검증
|
||||
|
||||
### 로그 레벨 관리
|
||||
- ERROR 레벨 로그가 실제 오류가 아닐 수 있음
|
||||
- 교훈: 외부 라이브러리 로그는 필요 시 레벨 조정
|
||||
|
||||
### 일시적 오류 vs 치명적 오류
|
||||
- BlockingIOError [Errno 11]은 일시적 리소스 부족으로 재시도 가능
|
||||
- 교훈: 오류 코드와 영향 범위 구분 필요
|
||||
114
troubleshooting/251016_naverworks_briefing_system_uuid_error.md
Normal file
114
troubleshooting/251016_naverworks_briefing_system_uuid_error.md
Normal file
@ -0,0 +1,114 @@
|
||||
# NaverWorks Briefing 'system' 사용자 UUID 오류
|
||||
|
||||
**날짜**: 2025-10-16
|
||||
**작성자**: Claude
|
||||
**관련 파일**: `rb8001/app/skills/naverworks_briefing.py`, `rb8001/app/state/database.py`
|
||||
|
||||
---
|
||||
|
||||
## 문제 상황
|
||||
|
||||
### 발생 시점
|
||||
- NaverWorks Daily Briefing 실행 시 (매일 09:00)
|
||||
- 감정 분석 결과 DB 저장 시도 중 (09:00:03.277)
|
||||
|
||||
### 에러 로그
|
||||
```
|
||||
{"time":"2025-10-16 09:00:03,277","level":"ERROR","module":"app.state.database"}
|
||||
Failed to save emotion reading: invalid input for query argument $1: 'system'
|
||||
(invalid UUID 'system': length must be between 32..36 characters, got 6)
|
||||
|
||||
{"time":"2025-10-16 09:00:03,277","level":"WARNING","module":"app.llm.emotion_llm"}
|
||||
Failed to save emotion reading for user system
|
||||
```
|
||||
|
||||
### 영향
|
||||
- emotion_reading 테이블 저장 실패
|
||||
- NaverWorks Briefing 자체는 정상 완료
|
||||
|
||||
---
|
||||
|
||||
## 원인 분석
|
||||
|
||||
### 1. 'system' 문자열 전달
|
||||
**위치**: naverworks_briefing.py:177 (추정)
|
||||
|
||||
감정 분석 호출 시 user_id로 'system' 문자열 전달:
|
||||
```python
|
||||
emotions = await emotion_classifier.classify_emotion(summary_text, user_id="system")
|
||||
```
|
||||
|
||||
### 2. DB 저장 시 UUID 검증
|
||||
**위치**: app/state/database.py
|
||||
|
||||
emotion_reading 테이블의 user_id 컬럼은 UUID 타입:
|
||||
```sql
|
||||
user_id UUID NOT NULL REFERENCES users(id)
|
||||
```
|
||||
|
||||
### 3. UUID 변환 실패
|
||||
asyncpg는 'system' 문자열을 UUID로 변환 불가 (길이 6자 < 32자)
|
||||
|
||||
---
|
||||
|
||||
## 해결 방안
|
||||
|
||||
### 1. user_id를 nullable로 변경 (DB)
|
||||
|
||||
**51123 서버에서 실행**:
|
||||
```sql
|
||||
ALTER TABLE emotion_reading ALTER COLUMN user_id DROP NOT NULL;
|
||||
```
|
||||
|
||||
### 2. 시스템 사용자 처리 로직 추가
|
||||
|
||||
**위치**: app/state/database.py:save_emotion_reading
|
||||
|
||||
**변경**:
|
||||
```python
|
||||
async def save_emotion_reading(user_id: str, emotions: List[str], ...):
|
||||
if user_id == "system":
|
||||
user_id = None # nullable로 저장
|
||||
await conn.execute("INSERT INTO emotion_reading ...")
|
||||
```
|
||||
|
||||
### 3. 전용 시스템 UUID 생성 (권장)
|
||||
|
||||
**51123 서버에서 실행**:
|
||||
```sql
|
||||
-- 전용 시스템 사용자 생성
|
||||
INSERT INTO users (id, username, email) VALUES
|
||||
('00000000-0000-0000-0000-000000000000', 'system', 'system@robeing.internal');
|
||||
```
|
||||
|
||||
**위치**: naverworks_briefing.py
|
||||
|
||||
**변경**: SYSTEM_USER_UUID 환경변수 사용
|
||||
|
||||
---
|
||||
|
||||
## 구현 완료
|
||||
|
||||
미구현 (감정 저장 실패만, 브리핑 기능 정상)
|
||||
|
||||
---
|
||||
|
||||
## 교훈
|
||||
|
||||
### UUID 타입 검증 누락
|
||||
- 함수 시그니처에서 user_id: str 허용하지만 실제는 UUID 필요
|
||||
- 교훈: 타입 힌트와 실제 검증 불일치 방지
|
||||
|
||||
### 시스템 사용자 처리 미정의
|
||||
- 사용자가 아닌 시스템 작업의 user_id 처리 정책 부재
|
||||
- 교훈: 시스템 사용자는 전용 UUID 또는 nullable 정책 수립
|
||||
|
||||
### DB 제약조건과 코드 불일치
|
||||
- DB는 NOT NULL이지만 코드는 'system' 문자열 전달
|
||||
- 교훈: DB 스키마와 코드 로직 동기화 검증 필요
|
||||
|
||||
---
|
||||
|
||||
## 참고 문서
|
||||
|
||||
- 250826_id_체계_정리_및_conversation_logs_문제_해결.md: UUID vs Slack ID 문제
|
||||
Loading…
x
Reference in New Issue
Block a user