국가별 영업일 발송 게이트
beta 실데이터 기반 최적 설계

수십만건 규모에서 "발송시 LLM으로 국가 판단" 우려를 실측으로 검증 — 비용·성능·오류 분석과 최적 아키텍처

데이터: beta postgres + redis 발송 표본: 최근 60일 / delayed 큐 97,216건 전수 작성: 2026-06-11

00 결론

✅ 핵심 발송시점 LLM은 전혀 불필요. sequence_step_executions.timezone이미 100% 저장(IANA 문자열, 잡 페이로드 동봉)돼 있어 발송 게이트는 $0 · 산술 연산으로 끝난다.
⛔ 진짜 문제 tz=Asia/Seoul 발송의 87%(10,998건)가 "수신자국을 이미 알면서도" 한국으로 잘못 디폴트됨. LLM이 아니라 country→tz 정적 변환이 누락된 버그에 가깝다.
⚠️ 설계 수십만건 최적해 = LLM을 발송경로에서 완전히 제거하고, 비싼 국가판단은 리드당 1회 · 도메인 캐시로 amortize. per-send LLM은 회피 대상.

01 실측 데이터 (beta)

100%
execution.timezone 채움률 (2.83M건) — 발송시 국가판단 불요
87%
tz=Seoul인데 country=해외(기지) → 잘못 디폴트 (10,998/12,648)
94.5%
수신자 해외 비중 (한국 5.5%) — "한국기준" 부적합
97,216
sequence-email delayed 큐 (미래예약 대기)

delayed 97,216건이 KST 언제 발사되나 (전수 계산)

주말 16.7% 평일 83.3%

업무외(18–09 KST) 발사예정 49.4% — 단 상당부분은 해외 수신자 현지 낮시간을 노린 의도된 결과(기존 clampToBusinessHours가 수신자 tz 기준).

수신자 국가 분포 (60일 발송)

국가비중국가비중
United States20.6%Australia3.8%
Indonesia8.0%Thailand3.7%
Japan6.3%Malaysia3.6%
South Korea5.5%Russia / Canada / Vietnam각 ~3%

02 사용자 우려 해부 — "발송시 LLM 국가판단"

우려: "수신자국 영업일로 하려면 LLM으로 국가 판단해야 하고, 수십만건이라 문제 많다." → 실데이터로 보면 세 단계 모두 LLM이 불필요:

단계실데이터LLM 필요?
발송시점 tz 확보execution.timezone 100% 저장, 잡에 동봉불요 (룩업)
87% 케이스 country 확보country 이미 기지(해외)인데 tz만 미변환불요 (정적맵)
잔여 미상 countryleads.country 미상 56.5% 중 ccTLD·도메인 해소 후 잔여잔여만·1회·캐시

03 최적 아키텍처 — LLM을 발송경로에서 제거

계층위치비용LLM
A. 발송 게이트pre-check.ts, 잡의 execution.timezone$0 (luxon 산술, μs급)
B. tz 해소 cascadeenrollment 시점, 리드당 1회거의 $0잔여만
C. 공휴일phase 2 데이터셋1회

계층 B — country→tz 우선순위 (싼 것부터, 결정론적)

  1. lead.country(기지) → IANA tz 정적맵 — 공짜. 10,998 오발 즉시 해소 (현재 누락, 최고 ROI)
  2. 이메일 ccTLD(.co.kr·.jp·.de) → country — 공짜·결정론적
  3. 도메인→country Redis 캐시 — 고유 도메인 ≪ 발송건수 → 도메인당 1회 해소 후 영구 재사용
  4. 잔여 모호 .com → 보수적 글로벌 디폴트 또는 배치 LLM(도메인당 1회·enrichment·영구 캐시) — LLM은 여기 한 곳, 발송경로 밖
// 발송 게이트: 잡에 이미 있는 tz로 순수 산술, LLM/DB/Redis 호출 0
const cal = businessCalendarFor(job.data.timezone)   // IANA 문자열
const w = decideSendWindow(new Date(), cal)        // luxon, DST 정확
if ('deferUntil' in w && cal.confident) {        // 디폴트 tz면 fail-open
  await job.moveToDelayed(w.deferUntil.getTime())
  throw new DelayedError()
}

04 비용 분석 (수십만건 1배치)

접근단가수십만건 비용채택
per-send LLM (우려안)발송마다 호출수십만 콜 ≈ $수십~수백 + 발송지연회피
발송 게이트(tz 룩업+산술)0$0채택
country→tz 정적맵0$0채택
잔여도메인 배치 LLM(1회·캐시)도메인당 1회수천 도메인 × 1회 ≈ 무시가능선택

성능: 게이트는 메모리 산술 1회. BullMQ가 어차피 delayed 97k를 순회하므로 추가 부하 사실상 0. DB·Redis 추가 호출 없음(tz는 페이로드 동봉). Redis Lua/Function 불필요.

05 예상 오류 & 방어

오류영향방어
tz=Seoul 디폴트(해외 87%)게이트가 한국기준 오판출시 전 계층B부터 적용. 그 전엔 디폴트 tz fail-open(92.6% 대량보류 방지)
DST 드리프트±1h 오발송고정오프셋 TZ_OFFSET 폐기, IANA+luxon
미국 6존·러시아 11존시간대 뭉뚱그림IANA가 구체값일 때만 신뢰, 디폴트는 fail-open
걸프권 금·토 주말잘못 평일판정어댑터 GULF set(일~목) 이미 존재
TLD 오판(.io·.com)발송시각만 어긋남(전달성 무관)저위험 — 디폴트 폴백
공휴일 미반영공휴일 발송 잔존phase 2 국가별 holiday set

06 권장 실행 순서 (ROI순)

  1. (공짜·최고ROI) country→tz 변환 복구 — 기지 country 10,998 오발 즉시 해소. LLM·인프라 0.
  2. (공짜) ccTLD + 도메인캐시 cascade — 미상 56.5% 잠식.
  3. 발송 게이트 출시execution.timezone+luxon, 디폴트 tz는 fail-open.
  4. (선택·저가) 잔여 도메인 배치 LLM — enrichment 1회·영구 캐시.
  5. (phase 2) 국가별 공휴일.
요지 발송 때 LLM 쓰는 설계 자체가 안티패턴이고 데이터상 그럴 필요도 없다(tz 100% 보유). 가장 큰 효과는 이미 아는 country를 tz로 바꾸는 누락을 공짜로 메우는 것.

07 데이터 출처

beta postgres send-grid-test-postgres-1 (emails·sequence_step_executions·leads·email_replies, 60일/97,216건 delayed 전수) · beta redis send-grid-test-redis-1 (bull:sequence-email delayed ZSET). 모두 2026-06-11 조회.