docs(troubleshooting): rb8001 LLM event-loop errors + coldmail domain parsing fix (co.kr) — findings, fixes, validation, lessons (KST 2025-10-24)

This commit is contained in:
Claude-51124 2025-10-24 14:37:31 +09:00
parent 4e9e758fd3
commit 419cac7a28

View File

@ -0,0 +1,60 @@
# rb8001 운영 트러블슈팅: LLM 이벤트 루프 오류 및 콜드메일 도메인 파싱 개선 (KST 2025-10-24)
## 상황 요약
- 8001(rb8001) 최근 로그에서 LLM 호출 실패 다수 확인: “Event loop is closed”, “coroutine … was never awaited”.
- Slack 콜드메일 피드백 흐름에서 리스트 생성 시 회사설명에 CO가 들어간 사례 발생(발신자: kmkim@koreage.co.kr, 제목: “해요코리아 IR 자료 송부의 건.”).
## 영향 범위
- LLM 경로: Slack 대화 처리 중 간헐적 실패 → 사용자에게 폴백 메시지 노출(“죄송합니다. 요청을 처리할 수 없습니다.”).
- 콜드메일: IR 지표가 N/A일 때 도메인 폴백이 CO로 잘못 표기되어 리스트 정보 품질 저하.
## 증거(근거 로그/지표)
- Docker(rb8001): ERROR “Gemini chat error: Event loop is closed”, RuntimeWarning “coroutine '..._invoke' was never awaited”.
- OpenSearch(dataprepper-static, UTC): 최근 6시간 내 rb8001에서 동일 에러 6건 확인.
- Health 체크: http://localhost:8001/health 200 OK, 관련 스킬(8505/8511/8515) healthy.
## 원인 분석
1) LLM 비동기 처리 버그(비동기/이벤트 루프 경계 문제)
- 비동기 API 호출 중 이벤트 루프 종료/부재 상황 발생 시 예외 처리 미흡 → 연쇄 실패.
- 일부 경로에서 await 누락으로 코루틴 미대기 경고 발생.
2) 도메인 폴백 파싱 오류
- koreage.co.kr에서 2레벨 추출 로직(parts[-2])이 CO를 회사명으로 오인 추출.
## 조치 사항(코드 수정)
1) LLM 안정화 및 로깅 개선
- GeminiHandler: async 실패(“Event loop is closed”, “no running event loop”, “was never awaited” 포함) 시 동기 `generate_content``run_in_executor`로 폴백하여 안전 재시도.
- 파일: `rb8001/app/llm/gemini_handler.py`
- 주요 변경: async → sync executor 폴백, `logger.exception`로 스택트레이스 기록.
- Router/LLMService 로깅 강화: `logger.exception` 사용으로 예외 스택 기록.
- 파일: `rb8001/app/router/router.py`, `rb8001/app/llm/llm_service.py`
2) 콜드메일 회사명 파싱 정확도 개선
- `tldextract` 도입으로 eTLD+1 기준 파싱: `koreage.co.kr` → domain=`koreage` 확보.
- 폴백 휴리스틱: 국가코드+세컨드레벨(co|com|or|ne|ac|go|gov|edu)+ccTLD 패턴 시 `parts[-3]` 사용.
- 파일: `rb8001/app/services/coldmail_processor.py`
- 의존성 추가: `tldextract>=5.1.2` (파일: `rb8001/requirements.txt`)
## 배포 및 검증
- Gitea 푸시 후 서버(51124)에서 `docker compose up -d --build`로 재기동.
- 헬스 확인: `curl http://localhost:8001/health` 200 OK, 관련 스킬 포트도 200 OK.
- 회귀 확인:
- 최근 1분 로그 기준 “Event loop is closed/never awaited” 미발견.
- 컨테이너 내 파싱 테스트: `tldextract.extract('koreage.co.kr')``domain='koreage', suffix='co.kr'` 정상.
## 커밋 참조
- fix(llm): executor 폴백 + 예외 로깅 강화 — `eb27d1b` → 정리 `59baa69`
- fix(coldmail): 도메인 파싱에 tldextract 적용 — `9629253`
## 후속 조치(제안)
- Slack 경로 실제 사용자 시나리오 재현 테스트(의도 UNKNOWN 빈도/LLM 실패율 모니터링).
- OpenSearch 대시보드/쿼리 저장: “Event loop is closed”, “never awaited” 알람 룰 추가(UTC↔KST 변환 유의).
- 9024(robeing-monitor) 헬스 엔드포인트 정합성 확인(404 응답 시 문서화 또는 구현).
## 교훈(📌 Lessons Learned)
- 비동기 호출은 이벤트 루프 수명/스레드 경계, await 일관성 확보가 핵심이다(폴백 경로 필수).
- 로깅은 스택트레이스까지 남겨야 원인 추적이 가능하다.
- 이메일 도메인 파싱은 공공 접미사 목록(eTLD) 기반 라이브러리 사용으로 오인식 방지.
— 작성: happybell80, KST 2025-10-24