어드민 콘솔의 "답장 잘 받는 메일" 페이지에서 발견된 4가지 결함(쿼리 속도·답장 카운트·Sheet 표시·렌더링) 과 후속 카운트 정확성 문제까지, 베타 DB 실측 기반으로 진단·개선해 alpha/beta 양쪽 머지를 완료한 리포트.
한 outbound 메일에 답장이 여러 번 와도 Sheet 의 카운트가 항상 "(1)" 로 보였던 문제. 베타 DB 실측으로 원인 확인:
| 방식 | multi-reply (≥2건) 잡힌 outbound | 최대 답장 수 (max) |
|---|---|---|
email_replies 매핑만 |
2건 / 124 | 2 |
thread_id 매칭 |
32건 / 124 (25%) | 5 |
| 둘을 UNION | 32건 (정확) | 5 |
email_replies 는 outbound 당 first reply 만 매핑하는 경향. 같은 thread 의 inbound 메일이 있어도 추가 매핑이 안 들어감. 사용자가 본 "(1)" 의 직접 원인. thread_id 매칭은 multi-reply 정확하지만 일부 케이스 매칭 누락 → 둘을 UNION 해 한 쪽에라도 잡히면 카운트.
v1 의 emails LEFT JOIN email_replies 후 GROUP BY 는 한 메일에 답장 N건일 때 emails 행이 N배로 복제 + email_replies 가 workspace 필터 못 받아 전체 Seq Scan 으로 cache 오염.
| 기간 | v1 (LEFT JOIN + array_agg) | v2 (단일 GROUP BY + MAX FILTER) |
|---|---|---|
| 7일 | ~30ms | 11ms |
| 30일 | 508~916ms | 410ms |
| 90일 | > 1s | < 1s |
array_agg(... ORDER BY ...) 가 31만 행 external merge sort (디스크 27MB) 강제 → MAX(id::text) FILTER (...) + UUIDv7 시간순 prefix 활용해 sort 제거.
초기 버전은 sanitizeEmailHtml + dangerouslySetInnerHTML 단순 호출 → email-replies / email-detail 페이지의 표준 파이프라인 (decode → DOMPurify → bracket 치환 → cid 이미지 → MIME 정리) 누락. 표준 EmailBody 컴포넌트로 교체해 다른 페이지와 동일 렌더.
emailId 를 nuqs useQueryState 로만 보관 → 클릭 직후 URL push 가 비동기라 첫 render 에 stale. useState 로 분리해 즉시 fetch.
/sequences/edit?id=... 캠페인 편집 페이지로 이동EmailBody) + 답장 미리보기 최신 3건모든 쿼리에서 활용된 인덱스:
emails_workspace_sent_at_idx (workspace_id, sent_at) — 캠페인/스텝 집계의 base scanemails_thread_id_idx (thread_id) — 답장 union 의 두번째 subqueryemail_replies_original_email_id_idx (original_email_id) — 답장 union 의 첫 subqueryemails_pkey (id) — preview 의 emails JOINUUIDv7 시간순 prefix 덕분에 MAX(id::text) FILTER (...) 가 latest = max — 별도 sort 회피. analyticsDb (work_mem 256MB) 사용으로 대규모 집계 디스크 spill 방지.