Rinda · 린다세일즈 워크스페이스 · 진단 리포트

리드 업로드 시 "기존 리드 유입" 이슈 분석

대상 그룹 한국브랜드 신규영업 (6.1) · 파일 korea_brand_email_unique_company_max3_crosschecked.csv · 2026-06-01

요약 (TL;DR)

1 무슨 일이 일어났나

파일 korea_brand_email_unique_company_max3_crosschecked.csv 는 GobizKOREA 기반 한국 수출기업 공식 홈페이지 이메일 수집분입니다. 7,160행, 이메일 전부 unique (파일 내부 중복 0), 회사 5,512, 도메인 3,756.

7,160
파일 행수 (파싱)
3,999
화면 "고유"
3,161
화면 "중복"
5,607
실제 그룹 멤버

화면의 "고유 3,999 / 중복 3,161"은 파일 내부가 아니라 워크스페이스 DB 기준 중복 판정입니다. 그런데 "중복 3,161"이 사용자에게는 "제외됨"으로 읽히지만 실제로는 그 중 1,852건이 그룹에 합류합니다.

그룹 멤버 5,607 ├─ 오늘 신규 생성 3,755 (고유 3,999 → 검증·실패 제외 244) └─ 기존 리드 재유입 1,852 ← 문제 지점 ├─ 타 그룹에도 소속 1,851 └─ 발송/반응 이력 보유 1,219 (contacted 1142 · bounced 58 · replied 14 · unsubscribed 5)

검증: 3,755 + 1,852 = 5,607 — 카운트가 정확히 맞아떨어짐.

2 왜 기존 리드가 유입되는가 (근본 원인)

업로드 UI AddBuyersPage(1 그룹 → 2 업로드 → 3 완료)는 smart-import 경로를 사용합니다. (별도 페이지인 lead-import 경로와 무관 — 그 경로엔 이 블록이 없음.)

단계위치
UI 제출StepMapping.tsx:337 → smartImportApi.startPipeline
APIPOST /api/v1/smart-import/start
서비스smart-import.service.ts:402 runSmartImportPipeline
문제 블록smart-import.service.ts:635-676
// smart-import.service.ts:635-676 — 중복(기존) 리드를 신규 그룹에 추가
if (customerGroupId && duplicates.length > 0) {
  const duplicateLeadIds = duplicates.map(d => d.matchedLeadId).filter(Boolean)
  // 이 그룹에 "이미 있는지"만 확인 — 그게 유일한 필터
  const toAdd = [...new Set(duplicateLeadIds.filter(id => !alreadyInGroupIds.has(id)))]
  db.insert(customerGroupMembers)
    .values(chunk.map(leadId => ({ groupId, leadId })))
    .onConflictDoNothing()   // ← lead_status·발송이력·타그룹 필터 전무
}

핵심: matchedLeadId는 이메일·URL·회사명 중 하나라도 워크스페이스 DB와 매칭되면 세팅됩니다. 그 기존 리드 전부가 — 발송했든, 수신거부했든, 다른 그룹 소속이든 — 무조건 신규 그룹에 합류합니다. 유일한 체크는 "이 그룹에 이미 있나"뿐입니다.

신규 그룹 생성 경로도 동일: StepGroupSelect가 그룹을 먼저 만들고 그 groupId를 넘기므로, 신규/기존 그룹 구분 없이 같은 블록이 실행됩니다.

3 중복 판단 로직 전수

실사용 경로 deduplicateRecords (smart-import.service.ts:257-397) — 우선순위 순차, 하나라도 hit이면 즉시 중복 처리:

순위기준매칭 대상정규화
1이메일lead_contacts (email) ∩ 같은 WSnormalizeEmail — NFC+trim+lowercase (gmail dot/plus 미정규화)
2website URLleads.website_url hostnormalizeUrlHost — hostname만, www. 제거
3회사명leads.company_namenormalizeCompanyName — 악센트·부호·법인접미사 제거

각 기준은 DB 중복 먼저, 없으면 CSV 파일 내 자가중복을 검사합니다. 워크스페이스 전체 leads/email을 메모리 Map으로 로드합니다.

케이스별 처리 매트릭스

케이스새 lead 생성기존 lead 업데이트그룹 멤버 추가이메일 컨택 추가
완전 신규생성추가추가
이메일만 DB 중복안 함안 함추가 ⚠안 함
URL만 DB 중복안 함안 함추가 ⚠안 함
회사명만 DB 중복안 함안 함추가 ⚠안 함
CSV 내부 이메일 중복안 함안 함안 함안 함
이메일 전부 검증 실패제외제외

기존 lead는 업데이트도, 새 컨택 추가도 전혀 하지 않습니다. DB 중복(matchedLeadId 보유)은 오직 그룹에만 추가, CSV 내부 중복(matchedLeadId 없음)은 그룹에도 추가 안 됨.

4 시나리오별 평가

"그룹에 합류한 뒤 실제 발송 시 차단되는가"가 심각도를 가릅니다. suppression 게이트가 enrollment 시점 + 발송 시점 이중으로 존재합니다 (bulk-enrollment-scheduling.service.ts:154 · resolve-lead.ts:102).

시나리오현재 동작실제 결과심각도
완전 신규 파일정상 생성·추가문제 없음없음
기존 리드 재업로드발송이력째 그룹 합류통계 오염·사용자 혼란중간
unsubscribed 포함그룹엔 합류하나 발송 직전 skip실발송 차단됨 (사고 아님). 단 enroll 통계 왜곡중간
bounced 포함그룹 합류, MV/blocklist에서 skip실발송 차단됨중간
5만행 대용량WS 전체 메모리 로드 + lower() seq-scan메모리 급증·지연 (실측 3분+ 미완)높음
페이지 이탈/재연결서버는 계속, SSE만 끊김진행 불명확하나 데이터는 안전낮음
MV 크레딧 고갈CreditsExhaustedError → 중단 / MX fallback부분 처리, 재개 불명확중간
검증 통과 이메일is_verified=false 저장발송 시 재검증 (MV 비용 2회)중간

정정: 초기에 "unsubscribed 재유입 = 컴플라이언스 발송 위험"으로 봤으나, 발송 경로 검증 결과 실제 재발송은 차단됩니다. 위험도를 하향합니다. 진짜 문제는 UX 혼란 + 그룹/enroll 통계 오염 + 검증 중복비용 + 대규모 성능입니다.

5 부가 발견

검증 결과 미영속 (is_verified)

5,891개 이메일 중 239개만 verified=true (그나마 과거 enrichment산), 5,652개 false. verifyImportEmails는 거절분만 반환하고 통과 이메일에 is_verified를 세팅하지 않음 (lead-import.service.ts:360-368 — isVerified 필드 누락). 발송 워커가 다시 검증 → MV 호출 2회 중복.

경로 이원화 (SSOT 위반)

smart-import(중복 그룹 추가) vs lead-import(중복 skip) 의 중복 정책이 정반대. 같은 "리드 업로드"인데 동작이 다름.

대규모 성능

dedup이 워크스페이스 전체 leads/email을 메모리 Map으로 로드 + lower(contact_value) seq-scan. 본 분석 중 교집합 쿼리가 3분+ 미완으로 종료됨 — 정규화 키 인덱스 부재가 직접 증거.

6 개선안

설계 원칙

#영역개선우선
A그룹 멤버십중복 리드를 3분류(신규 / 기존-발송가능 / 기존-suppressed)로. 업로드 옵션 "기존 리드 처리: 신규만 ┃ 기존도 합류 ┃ 전체", 기본=신규만높음
B검증 SSOTapproved Set 반환 → is_verified+verified_at+score 저장. 발송 워커는 90일내 검증분 재호출 skip높음
Cdedup 성능정규화 키 generated column + 인덱스, unnest 배치 JOIN으로 전환높음
DUX/문구"신규 N 추가 / 기존 N 합류 / 제외 N" 3분류 + 완료화면 최종 그룹크기·발송이력 보유수중간
E안정성업로드를 BullMQ 잡으로, 진행상태 DB SSOT, SSE는 표시 전용 (재연결·재시도 안전)중간

착수 추천

N0.1 정규화 키 인덱스(C) + A 그룹 합류 게이팅 묶음을 첫 PR로. 인덱스는 단독으로도 즉효, A는 혼란의 직접 원인.

Rinda 내부 진단 리포트 · 자동 생성 · 비공개(noindex) · 2026-06-01