테마
Epic Spec: E-09 미사용 상품 정리
메타
| 항목 | 값 |
|---|---|
| Epic ID | E-09 |
| Sprint | S52 |
| 상태 | approved |
| PO 승인일 | 2026-01-20 |
| 담당 | TBD |
1. WHY: 왜 이 기능이 필요한가?
문제 정의
셀러들이 정식 운영 전 테스트 상품을 다수 등록하고, 반응 없는 상품은 쿠팡/스마트스토어에서 삭제하지만, 장사왕은 한 번 수집한 상품을 삭제하지 않음.
[현재 상황]
1. 셀러가 쿠팡에 상품 100개 등록
2. 장사왕이 100개 수집
3. 셀러가 쿠팡에서 70개 삭제 (판매 부진)
4. 장사왕에는 여전히 100개 존재
5. 원가입력 화면에 70개의 "유령 상품" 노출
[누적 효과]
• 오래된 셀러일수록 상품 수가 매우 많아짐
• 실제 판매 상품 30개 vs 장사왕 상품 100개
• "어떤 상품에 원가를 입력해야 하지?" 혼란근거 데이터
| 지표 | 수치 | 출처 | 비고 |
|---|---|---|---|
| Product 테이블 총 row 수 | 658만 개 | DB | snapshot 기준 |
| 총 벤더 수 | ~30,000개 | DB 인덱스 | cardinality 기준 |
| 평균 상품 수/벤더 | ~219개 | 계산값 | |
| Product 테이블 크기 | 8.9GB | DB | 데이터+인덱스 |
| 원가입력률 | TBD | DB | 상품 수 많을수록 낮을 것으로 예상 |
⚠️ Product 테이블이 매우 무거워 직접 쿼리 어려움. Full scan 절대 금지.
기대 효과
- 셀러 UX 개선: 원가입력 화면에 실제 판매 상품만 노출
- 원가입력률 증가: 정리된 화면 → 입력 부담 감소
- 시스템 효율: Product 테이블 사이즈 감소 → 쿼리 성능 개선
- 비용 절감: DB 스토리지 비용 감소 (장기)
2. WHAT: 상세 요구사항
핵심 기능
| # | 기능 | 설명 | 필수 여부 |
|---|---|---|---|
| 1 | 상품 삭제 기능 | 원가입력 화면에서 개별 상품 삭제 | 필수 |
| 2 | 일괄 삭제 기능 | 여러 상품 선택 후 일괄 삭제 | 필수 |
| 3 | 미사용 상품 필터 | 최근 N일간 판매 없는 상품 필터링 (참고용) | 필수 |
| 4 | 삭제 취소 (복구) | 실수로 삭제한 상품 복구 (7일 내) | 필수 |
| 5 | 영구 삭제 | 7일 경과 후 자동 영구 삭제 (Hard Delete) | 필수 |
핵심 원칙: 미사용 기준은 장사왕이 강제하지 않음.
필터는 참고용으로 제공하고, 삭제 여부는 셀러가 직접 선택.
비즈니스 로직
로직 1: 미사용 상품 판별
[미사용 상품 조건]
최근 N일간 판매 없음 (N = 90일 기본값)
판별 쿼리 예시:
SELECT p.id, p.name
FROM Product p
LEFT JOIN Settlement s ON p.id = s.product_id
AND s.settlement_date >= NOW() - INTERVAL 90 DAY
WHERE p.seller_id = ?
AND s.id IS NULL
[주의]
• 신규 등록 상품은 제외 (등록 후 30일 미만)
• 계절 상품 고려 → 90일은 최소 기준, 셀러가 조정 가능로직 2: 삭제 처리 (Soft Delete → 7일 후 Hard Delete)
[삭제 방식]
즉시 Hard Delete ❌ → 복구 불가, 실수 위험
Soft Delete ✅ → deleted_at 컬럼 활용 + 7일 유예
[삭제 시 (Soft Delete)]
Product.deleted_at = NOW()
Product.deleted_by = 'seller'
[영구 삭제 (Hard Delete)]
• 삭제 후 7일 경과 → 자동 영구 삭제 (배치)
• 별도 아카이브 테이블 불필요 ✅
• 유저 요청에 의한 삭제이므로 장기 보관 불필요
[근거]
• 유저가 직접 삭제 요청 → 유저 의사 존중
• 7일 유예 기간 → 실수 복구 충분
• 상품 데이터 = 개인정보 아님 → 별도 보관 의무 없음로직 3: 복구 로직
[복구 가능 조건]
• deleted_at으로부터 7일 이내
• 아직 ProductArchive로 이동 전
[복구 시]
Product.deleted_at = NULL
Product.deleted_by = NULL사용자 시나리오
[시나리오 1: 개별 삭제]
1. 셀러가 원가입력 페이지 진입
2. 상품 목록에서 더 이상 판매하지 않는 상품 발견
3. 상품 행의 [...] 메뉴 클릭 → "삭제" 선택
4. "정말 삭제하시겠습니까?" 확인
5. 삭제 완료 → 목록에서 제거
6. 토스트: "삭제되었습니다. 7일 내 복구 가능"
[시나리오 2: 미사용 상품 일괄 삭제]
1. 셀러가 원가입력 페이지 진입
2. 필터: "최근 90일 판매 없음" 선택
3. 해당 상품들만 필터링되어 표시
4. "전체 선택" 체크
5. "선택 삭제" 클릭
6. "15개 상품을 삭제하시겠습니까?" 확인
7. 삭제 완료
[시나리오 3: 실수 복구]
1. 셀러가 실수로 판매 중인 상품 삭제
2. 상단 알림: "최근 삭제한 상품 복구하기"
3. 클릭 → 최근 삭제 목록 표시
4. 복구할 상품 선택 → "복구" 클릭
5. 원가입력 목록에 다시 표시3. HOW: 플로우 & 화면
사용자 플로우
[원가입력 페이지]
│
├─→ [필터: 미사용 상품] ─→ [미사용 상품 목록]
│ │
│ ├─→ [전체 선택] ─→ [일괄 삭제]
│ │
│ └─→ [개별 선택] ─→ [개별 삭제]
│
└─→ [개별 상품 메뉴] ─→ [삭제]
│
▼
[확인 모달]
│
▼
[삭제 완료]
│
▼
[복구 가능 안내 토스트]와이어프레임
[원가입력 페이지 - 필터 추가]
┌───────────────────────────────────────────────────────────────┐
│ 원가 입력 │
├───────────────────────────────────────────────────────────────┤
│ │
│ [검색: 상품명/SKU] [필터 ▼] [📦 미사용 상품 15개] │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ ☐ │ 상품명 │ SKU │ 최근 판매일 │ 원가 │ ⋯ │ │
│ ├───┼─────────────────┼──────────┼─────────────┼──────┼───┤ │
│ │ ☐ │ 프리미엄 티셔츠 │ SKU-001 │ 2026-01-15 │ 15,000│ ⋯│ │
│ │ ☐ │ 기본 티셔츠 │ SKU-002 │ 2025-10-01 │ - │ ⋯ │ │ ← 90일 이상
│ │ ☐ │ 테스트 상품 │ SKU-003 │ - │ - │ ⋯ │ │ ← 판매 없음
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ [선택 삭제] 1-50 / 127 │
│ │
└───────────────────────────────────────────────────────────────┘
[미사용 상품 필터 적용 시]
┌───────────────────────────────────────────────────────────────┐
│ 원가 입력 │
├───────────────────────────────────────────────────────────────┤
│ │
│ [검색: 상품명/SKU] [필터: 미사용 상품 ✕] │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 💡 최근 90일간 판매가 없는 상품입니다. │ │
│ │ 이 상품들을 정리하면 원가입력이 더 쉬워져요. │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ ☑ │ 상품명 │ SKU │ 최근 판매일 │ 원가 │ ⋯ │ │
│ ├───┼─────────────────┼──────────┼─────────────┼──────┼───┤ │
│ │ ☑ │ 기본 티셔츠 │ SKU-002 │ 2025-10-01 │ - │ 🗑│ │
│ │ ☑ │ 테스트 상품 │ SKU-003 │ - │ - │ 🗑│ │
│ │ ☑ │ 구모델 신발 │ SKU-015 │ 2025-09-15 │ 25,000│ 🗑│ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ [☑ 전체 선택 (15)] [🗑 선택 삭제] 1-15 / 15 │
│ │
└───────────────────────────────────────────────────────────────┘
[삭제 확인 모달]
┌───────────────────────────────────────────────────────────────┐
│ │
│ 🗑️ 상품 삭제 │
│ │
│ 15개 상품을 삭제하시겠습니까? │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ • 기본 티셔츠 (SKU-002) │ │
│ │ • 테스트 상품 (SKU-003) │ │
│ │ • 구모델 신발 (SKU-015) │ │
│ │ • ... 외 12개 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 💡 삭제 후 7일 내에 복구할 수 있습니다. │
│ │
│ [취소] [삭제하기] │
│ │
└───────────────────────────────────────────────────────────────┘
[삭제 완료 토스트]
┌───────────────────────────────────────────────────────────────┐
│ ✅ 15개 상품이 삭제되었습니다. [복구하기] [✕] │
└───────────────────────────────────────────────────────────────┘개별 상품 메뉴
[상품 행 우측 ... 메뉴]
┌─────────────────┐
│ 📝 원가 수정 │
│ 📋 상품 상세 │
│ ─────────────── │
│ 🗑️ 삭제 │
└─────────────────┘디자인 요청사항
| 항목 | 내용 |
|---|---|
| 톤앤매너 | 정리/청소 느낌, 부담 없이 |
| 미사용 배지 | 회색 톤, "미사용" 또는 "90일+" |
| 삭제 버튼 | 빨강 아님, 회색/중립 (부정적 느낌 최소화) |
| 복구 안내 | 녹색, 안심 메시지 |
4. EDGE: 예외 & 엣지 케이스
데이터 예외
| 상황 | 처리 방법 |
|---|---|
| 상품에 원가가 입력되어 있음 | 삭제 가능 (원가도 함께 삭제) |
| 상품에 정산 데이터가 연결됨 | 삭제 가능 (정산 데이터는 유지, 상품 참조만 해제) |
| 삭제 후 쿠팡에서 재판매 시작 | 다음 수집 시 새 상품으로 등록 |
| 이미 삭제된 상품 중복 삭제 시도 | 무시 (이미 삭제됨) |
사용자 예외
| 상황 | 처리 방법 |
|---|---|
| 실수로 활성 상품 삭제 | 7일 내 복구 가능 안내 |
| 복구 기간(7일) 경과 | 복구 불가 안내, 다음 수집 시 자동 재등록 안내 |
| 전체 상품 삭제 시도 | 경고: "모든 상품을 삭제하면 원가입력을 할 수 없습니다" |
시스템 예외
| 상황 | 처리 방법 |
|---|---|
| 아카이브 배치 실패 | 재시도 (3회), 실패 시 알림 |
| 대량 삭제 시 타임아웃 | 비동기 처리, 완료 시 알림 |
5. 성공 지표
이 에픽의 성공 기준
| 지표 | Before | Target | 측정 방법 |
|---|---|---|---|
| 삭제 기능 사용률 | 0% | 30%+ | 삭제 실행 셀러 / 전체 활성 셀러 |
| 평균 상품 수 감소 | TBD | -30% | Product 테이블 row 수 |
| 원가입력률 변화 | TBD | +5%p | 원가 입력된 상품 / 전체 상품 |
| Product 테이블 사이즈 | TBD | -20% | DB 스토리지 |
GA4 이벤트
| 이벤트명 | 트리거 | 파라미터 |
|---|---|---|
product_delete_click | 삭제 버튼 클릭 | seller_id, product_count |
product_delete_confirm | 삭제 확인 | seller_id, product_count |
product_delete_cancel | 삭제 취소 | seller_id, product_count |
product_restore | 복구 실행 | seller_id, product_count |
unused_filter_apply | 미사용 필터 적용 | seller_id, unused_count |
6. 의존성 & 제약
기술 의존성
| 의존 항목 | 상태 | 비고 |
|---|---|---|
| Product 테이블 스키마 변경 | ⏳ 필요 | deleted_at, deleted_by 컬럼 추가 |
| 영구 삭제 배치 작업 | ⏳ 개발 필요 | 7일 경과 상품 Hard Delete |
| Settlement 조회 쿼리 | ✅ 기존 사용 | 최근 판매일 계산용 |
제약 조건
- Product 테이블 무거움: 658만 row, 8.9GB → Full scan 금지, 인덱스 활용 필수
- Soft Delete 적용: 기존 쿼리에
WHERE deleted_at IS NULL추가 필요 - 삭제 배치 주의: 대량 DELETE 시 락 최소화 (batch 처리)
확인 필요 사항
| 항목 | 담당 | 상태 |
|---|---|---|
| Product 테이블 현재 row 수 | Danny | ✅ 658만 row |
| 미사용 상품 비율 분석 | Danny | ⏳ 쿼리 무거움, 샘플링 필요 |
| 삭제 상품 보관 기간 | PO | ✅ 7일 후 영구삭제 (유저 요청) |
7. 스토리 목록 (예정)
Epic Spec 승인 후 분해
| Story ID | 제목 | 규모 | 상태 |
|---|---|---|---|
| E-09-S-01 | Product 테이블 스키마 변경 (soft delete) | S | draft |
| E-09-S-02 | 상품 삭제 API (개별/일괄) | M | draft |
| E-09-S-03 | 상품 복구 API | S | draft |
| E-09-S-04 | 미사용 상품 필터 API | M | draft |
| E-09-S-05 | 원가입력 UI - 삭제/복구 기능 | M | draft |
| E-09-S-06 | 원가입력 UI - 미사용 필터 | M | draft |
| E-09-S-07 | 영구 삭제 배치 (7일 경과) | S | draft |
| E-09-S-08 | 기존 쿼리 soft delete 대응 | M | draft |
체크리스트
PO 승인 전 체크
- [x] 문제 정의가 명확한가?
- [x] 비즈니스 로직이 구체적인가? (Soft Delete → 7일 후 Hard Delete)
- [x] 와이어프레임/플로우가 있는가?
- [x] 엣지 케이스가 정의되었는가?
- [x] 성공 지표가 측정 가능한가?
- [x] 기술 의존성이 확인되었는가? (Product 658만 row 확인)
핵심 결정 사항 (확정)
| 항목 | 결정 | 근거 |
|---|---|---|
| 미사용 기준 | 셀러가 직접 선택 | 필터는 참고용, 강제하지 않음 |
| 삭제 후 보관 | 7일 후 영구삭제 | 유저 요청 삭제, 장기 보관 불필요 |
| 아카이브 테이블 | 불필요 | 7일 유예면 충분, 복잡도 감소 |
미결 사항 (개발 단계에서 진행)
| 항목 | 상태 | 담당 |
|---|---|---|
| 미사용 상품 비율 샘플링 | ⏳ 선택사항 | Danny |
승인
| 항목 | 확인 |
|---|---|
| PO 승인 | ☑️ |
| 승인일 | 2026-01-20 |
| 비고 | 셀러 선택권 존중, 7일 후 영구삭제 |
작성일: 2026-01-20최종 수정: 2026-01-20
