2026-06-15 · PR #8598 (설정 UI) · Actions run 27517617074 · 작성자 분석
RecipientSendTimeLearning 데일리 잡이 worker 프로세스를
frozen(멈춤) 시켜 unhealthy로 2일 방치 → FE-only 배포가 그 worker를 recreate 하지 않아 헬스 게이트가 막힌 것.
worker 재생성 후 재배포 성공 (CD Pipeline Beta = success) ✅
ensureRecipientSendTimeLearningCron 을 removeJobScheduler 로 바꿔 Redis 데일리 스케줄러 제거 →
재발 차단. 기존 추천 데이터·조회 경로는 유지(학습 갱신만 중단).
| 단계 | 사실 |
|---|---|
| worker 상태 | bullmq-worker-139 "Up 2 days (unhealthy)", 마지막 로그 약 54시간 전 = frozen |
| 헬스체크 | /livez curl 이 2일째 exceeded timeout (2s) — 이벤트 루프 블록으로 HTTP 무응답 |
| 왜 방치되나 | restart: always 는 exit 시에만 작동. frozen(살아있지만 멈춤)은 재시작 트리거 안 됨 |
| 왜 배포 실패 | FE-only 변경 → worker 이미지 불변 → compose 가 recreate 안 함 → unhealthy worker 잔존 → "모든 서비스 healthy" 게이트 240s 타임아웃 → 롤백 |
도입: 04387be37 (lsuminl, 2026-05-27, #7915 "회사별 추천 발송 시간") · cron: 매일 KST 03:20 전역 실행
(workspaceId 없이 호출 → 전 워크스페이스 90일치 전량 재계산)
// 110만 행을 동기 map + 각 행 new Date() const stats = rowsOf<StatRow>(raw).map(mapStatRow) // chunk(stats, 500) 도 110만 항목 동기 슬라이스수 초간 이벤트 루프 정지 → heartbeat
setInterval(5s) 와 /livez HTTP(:3010) 응답 불가 → docker 2s 타임아웃 초과.
mem_limit 4g + 무 swap
(memswap_limit 4g, mem_swappiness 0). Bun(JavaScriptCore)은 V8식 heap cap 도 없음 →
RSS 급등 시 GC 가 계속 돌며 프로세스가 exit 없이 멈춤 → restart: always 도 안 걸려 2일 방치.
workspaceId 없이 호출 → 서비스가 전역 경로(DELETE ALL 후 전체 재삽입)로 90일치 전 워크스페이스 재계산.
데이터 증가에 선형 악화 (현재 statCount 1,126,658). 증분이 아니라 매일 전량 재구축.
for … await db.insert(chunk) 2,253회 순차 round-trip.
회사(수신자)별 최적 발송 시간 추천 — 시퀀스 이메일을 "받는 사람이 가장 잘 여는 현지 시간대"에 보내려는 기능. 과거 발송·반응 데이터를 학습해 리드/도메인/국가 단위로 추천 시각을 만든다.
| 구성요소 | 역할 |
|---|---|
| 학습 (learning.service) | 최근 90일 emails+email_events(open/click/reply)를 단일 거대 CTE로 집계 → recipient_send_time_stats(요일×시간 버킷별 발송·오픈·클릭·답장 카운트) 재구축. 매일 KST 03:20 전역 실행. |
| 추천 (recommendation.service) | 버킷 통계를 점수화해 scope별 최적 시각 1개 선정 → recipient_send_time_recommendations. 점수 = 발송성과 0.6 + 반응활동 0.4, 업무시간(평일 8–18시)만 후보. |
| 스코프 우선순위 | lead(샘플≥20) → email_domain(≥100) → country_timezone(≥200) → 부족하면 기본 10:00. 좁은 스코프 우선. |
| 소비처 | sequences.routes / bulk-enrollment-scheduling 가 getSendTimeRecommendationsForLeads 로 리드별 추천 시각 조회 → 발송 스케줄에 반영. 미리보기는 previewSendTimeRecommendations. |
점수식: 오픈율×0.35 + 클릭율×0.25 + 답장율×0.40 (답장 가중 최대). confidence = 샘플신뢰 0.7 + 점수신뢰 0.3.
도입 #7915 (lsuminl, 2026-05-27). 비활성화 영향: 기존 추천은 계속 사용되고 일일 재학습만 멈춤 — 데이터가 점차 오래될 뿐 발송은 정상.
.map/chunk 사이에 yield-to-event-loop (이미 utils/yield-to-event-loop.ts 존재) 끼워 이벤트 루프 양보 → heartbeat 유지INSERT … SELECT 로 DB 내 처리 (왕복·메모리 제거)근본 해결: worker 분할 + INSERT…SELECT