내부 검토용 제안 · 허브앤스포크 구현 상세 (AFC 피드백) →
상품 마스터 데이터(MDM) 설계 제안서

상품은 한 곳에서만
관리한다 — 허브앤스포크 MDM

같은 상품을 Foodil·Quoteapp에 중복 관리해 생기는 드리프트를 없앱니다. 신규 상품 마스터 어드민을 단일 진실로 두고, Foodil·Quoteapp은 단방향 동기화로 투영(projection)을 받아 “읽기 위주”로 소비합니다. CIPL은 문서 생성 서비스로 그대로 둡니다. DB를 합치는 것이 아니라, 상품을 한 번만 관리하는 구조입니다.

대상 Foodil · Quoteapp (+ CIPL 문서) · 목표 상품 3중 관리 제거 · 방식 허브앤스포크 MDM · 단방향 동기화 · 작성 2026-06-17
개요 · Overview

DB를 합치는 게 아니라, 한 번만 관리한다

진짜 문제는 “같은 상품이 여러 곳에서 관리돼 어긋난다”입니다. 이는 마스터 데이터(MDM) 문제이지, 세 DB를 하나로 융합하는 문제가 아닙니다. 그래서 회사 카탈로그의 단일 작성 지점(상품 마스터 어드민)을 두고, Foodil·Quoteapp은 그 투영을 받습니다. 각 앱의 DB·인증·주문/견적은 그대로 두고, 상품 데이터만 마스터로 모읍니다.

한 번만 관리
상품 마스터 = 단일 작성 지점
DB-native 배포
live read · 복제 (이벤트 파이프라인 없음)
회사 카탈로그 범위
Foodil 셀러 상품은 건드리지 않음
점진적 롤아웃
Quoteapp 먼저 · 빅뱅 없음
현행 & 새 역할 · Roles

세 시스템 — 새 구조에서의 역할

색으로 시스템을 구분합니다. 새 구조에서 누가 소비자(투영)이고 누가 문서 서비스인지를 함께 표시했습니다.

시스템 ①
Foodil

B2B 마켓플레이스. 회사 상품은 마스터 투영을 받고, 셀러 상품은 지금처럼 Foodil 자체 관리. 주문·스토어·가격정책·임베딩·RLS는 그대로 로컬.

소비자 (투영)
시스템 ②
Quoteapp

내부 견적/PO ERP. 지금은 JSON 파일이 DB 역할. 정식 Postgres로 전환하고 카탈로그 전체를 마스터 투영으로 받음. 견적·PO·원장은 로컬.

소비자 (투영 · 신규 DB)
시스템 ③
CIPL Site

상업송장/포장명세서 도구. 회사 상품은 Quoteapp export로 문서 생성 시점에만 전달됨. 외부 고객의 비공개 카탈로그는 범위 밖.

문서 서비스 (동기화 X)

핵심 범위: “3중 관리”의 실체는 회사 카탈로그가 Foodil과 Quoteapp에 중복된다는 것입니다. 마스터는 회사 상품만 다스리고, Foodil의 다른 셀러 상품은 그대로 둡니다. CIPL은 이미 Quoteapp이 문서 시점에 데이터를 밀어주므로 별도 동기화 대상이 아닙니다.

방안 비교 · Decision

왜 MDM인가 — 세 가지 방안 비교

이 선택은 기술 취향이 아니라 전략 문제입니다. 두 가지가 답을 결정합니다 — (1) 세 앱이 하나의 플랫폼으로 수렴하는가, (2) 드리프트가 실제로 얼마나 큰가(공유 상품 수 × 변경 빈도 × 오류 비용). 아래는 두 극단(전체 통합 · 분리 유지)과 그 사이의 MDM을 정직하게 비교한 것입니다.

방안 A
전체 통합

세 앱을 하나의 DB로 합치고 모두 그 DB로 재연결.

전략적 베팅
장점 (이게 맞다면)
  • 단일 진실 · 동기화 불필요 (sync 인프라 0)
  • 운영 표면 1개 — 백업·보안·모니터링·스케일
  • 교차 시스템 기능이 단순 조인 (federation 불필요)
  • 상품뿐 아니라 고객·공급사·브랜드까지 통합
적합한 경우
  • “하나의 플랫폼으로 운영”이 이미 결정됨 (다른 앱 sunset)
  • 사용자 풀이 실제로 겹치고, 한 팀이 셋 다 소유
  • 일회성 대규모 앱 재작성을 감수
비용 · 리스크
  • 세 앱의 릴리스 주기 결합 → 한 마이그레이션이 셋 다 위험(blast radius 3배)
  • 빅뱅 컷오버 · 전면 앱 재작성 동시 진행
  • 외부 고객 PII(CIPL)+내부 마진(Quoteapp)+공개 마켓을 한 auth·한 RLS에 → 실수 1번이 회사 간 누출
  • 데이터 위생 문제에 전략적 결합 리스크를 떠안음
방안 B
MDM 허브앤스포크

신규 상품 마스터를 단일 작성 지점으로, 앱은 단방향 동기화로 투영 소비.

권장
장점
  • “한 번만 관리” 달성 — 드리프트 제거(목표 직접 해결)
  • 각 앱의 DB·인증·RLS·릴리스 주기 독립 유지 (blast radius 격리)
  • 점진적 롤아웃 · 빅뱅 없음 · 되돌리기 쉬움
  • CIPL 외부 고객·PII 모델 무변경
적합한 경우
  • 드리프트는 실재하지만 앱들이 전략적으로 구분
  • 회사 카탈로그 규모가 중간 이상
  • 마스터를 소유할 팀/책임자가 있음
비용 · 리스크
  • 신규 동기화 계층 + 마스터 어드민을 만들고 운영해야 함
  • 최종 일관성(분 단위 지연) · “마스터만 작성” 규율 강제 필요
  • 드리프트가 아주 작으면 과설계일 수 있음 → 더 가벼운 버전 가능
방안 C
분리 유지

아무것도 통합하지 않고 지금처럼 각자 관리.

최소 · 조건부
장점
  • 엔지니어링 0 · 새 시스템 소유 부담 없음
  • 각 앱이 단순 · 독립 유지
  • 드리프트가 정말 작다면(예: 공유 50건·월 몇 회) MDM은 YAGNI
적합한 경우
  • 드리프트 비용이 미미
  • 앱들이 전략적으로 갈라지는 중 (결합 투자 무의미)
비용 · 리스크 (놓치는 것)
  • Quoteapp이 정식 DB 없이 JSON 파일 1개 → 통합과 무관한 운영 리스크(제약·트랜잭션·백업 없음)
  • 상품 오류가 무역 문서로 전파(Quoteapp→CIPL): HS code·중량·가격 오류 = 통관·결제 비용
  • 영구 드리프트 수용 = 고치겠다던 바로 그 문제를 방치
방안비용·노력리스크드리프트 해소적합한 경우
A · 전체 통합높음높음 (회사 간 누출)완전하나의 플랫폼으로 수렴 결정 시
B · MDM 허브앤스포크 권장중간낮음 (격리)완전 (점진)구분된 앱 + 실재하는 드리프트
C · 분리 유지0 (+ Quoteapp DB)중간 (오류 전파)없음드리프트 미미 + 전략적 분기
권장 · Recommendation
전략 결정이 없다면 MDM, 드리프트가 작으면 최소안
  • 전체 통합은 보류 — “하나의 플랫폼” 전략이 확정되지 않았다면, 데이터 위생 문제를 풀려고 세 시스템 결합 + PII/auth 혼합 리스크를 떠안는 건 과합니다. 이는 데이터 수정이 아니라 전략적 베팅입니다.
  • 분리 유지도 부적절 — Quoteapp 무(無)DB와 무역 문서 오류 전파라는 두 실재 리스크 때문에 “그냥 둔다”는 사실상 무료가 아닙니다.
  • 정답은 드리프트를 없애는 가장 가벼운 MDM — 드리프트가 아주 작다면 더 가벼운 최소안(Quoteapp에 DB + 한 시스템이 작성 + 나머지 야간 import)으로 시작해, 필요 시 풀 허브앤스포크로 성장.
  • 결정적 한 가지 숫자공유 상품 수 × 변경 빈도 × 오류 비용. 작으면 최소안, 크면 풀 허브앤스포크. 빌드 전에 이 값을 먼저 추정하는 것을 권장합니다.
확정 결정 · Decisions

합의된 전제 — 자체 평가 후 방향 전환

초기엔 “단일 DB 통합”을 검토했으나, 목표가 “상품 3중 관리 제거”(마스터 데이터 문제)로 확정되면서 더 안전하고 점진적인 허브앤스포크 MDM으로 전환했습니다.

확정

목표

상품 3중 관리 제거

DB 융합이 아니라 마스터 데이터(MDM) 문제. 한 곳에서 작성, 나머지는 소비.

확정

토폴로지

허브앤스포크

신규 상품 마스터 어드민 + core DB = 단일 작성 지점(허브). 소비자는 스포크.

확정

소비자

Foodil·Quoteapp 투영

단방향 동기화로 투영 수신. 각자 DB·인증·주문/견적은 그대로 유지.

확정

CIPL

문서 서비스

Quoteapp export로 문서 생성. 동기화 대상 아님. 외부 고객 카탈로그 제외.

확정 05

마스터 범위

회사 카탈로그만

Foodil 셀러 상품은 그대로. master-fed 행만 master_product_id로 표시.

권장 06

배포

DB-native (이벤트 없음)

CIPL live read · Foodil logical replication+overlay · Quoteapp 직접. 자체 outbox/pg_net 파이프라인 없음.

확정 07

작성 권한

마스터만 작성

소비자는 master-fed 행의 master 필드 읽기전용. 이게 “한 번만 관리”의 핵심.

권장 08

시드

일회성 정합

Foodil+Quoteapp 카탈로그를 1회 병합(중복 제거) 후 동기화 가동.

확정 09

롤아웃

Quoteapp 먼저 (thin slice)

DB 없는 내부 도구라 위험 최소. 검증 후 Foodil 소비자 연결. 빅뱅 없음.

아키텍처 · Architecture

단일 마스터 · 두 투영 · 한 문서 서비스

One master, two projections, one doc service.

상품의 작성/편집은 마스터 어드민 한 곳에서만 일어납니다. Foodil과 Quoteapp은 마스터에서 단방향 동기화로 투영을 받아 “읽기 위주”로 소비합니다. 각 앱은 자기 DB·인증·RLS·주문/견적을 그대로 유지합니다.

마스터는 회사 카탈로그만 다스립니다. Foodil의 다른 셀러 상품은 영향이 없고, 마스터에서 온 행만 master_product_id로 표시되어 마스터 소유 필드가 읽기전용이 됩니다. 그래서 “편집기 회수”는 “Foodil 상품 UI 동결”이 아니라 “master-fed 행의 master 필드만 잠금”입니다.

배포는 DB 네이티브로 합니다 — 자체 이벤트 파이프라인(outbox/pg_net)을 만들지 않습니다. CIPL은 문서 시점 live read(항상 최신), Foodillogical replication으로 읽기전용 사실 미러를 받아 로컬 overlay(공개여부·스토어·가격정책·rich description)와 JOIN, Quoteapp 어드민은 core를 직접 read/write. 소유는 컬럼이 아니라 테이블 단위로 나눠 로컬 데이터 덮어쓰기 사고를 막습니다.

마스터 (core)

  • core.products + brands·categories
  • core.product_prices (원가·기준가)
  • core.product_sources (계보)
  • 전용 하드닝 DB · 백업 · 격리

동기화

  • CIPL — 문서 시점 live read
  • Foodil — logical replication + overlay
  • Quoteapp — core 직접 read/write

소비자 (투영)

  • 기존 products 테이블 유지
  • master 필드만 갱신 · 로컬 필드 보존
  • FK·RLS·스냅샷 그대로

시드 · 롤아웃

  • 일회성 정합 (중복 1회 제거)
  • Quoteapp 먼저 → Foodil
  • thin slice로 루프 검증
DIAGRAM 01

허브앤스포크 토폴로지 — 한 곳에서 작성, 나머지는 소비

상품은 마스터 어드민에서만 작성/편집됩니다(허브). Foodil·Quoteapp은 단방향 동기화로 투영을 받습니다(스포크). CIPL은 Quoteapp export로 문서 생성 시점에만 데이터를 받는 다운스트림 서비스입니다.

전용 core 마스터 DB + 얇은 어드민 단일 작성 지점 · Quoteapp 시드 · 사실의 단일 진실 products · brands · prices · sources (전용 하드닝 DB) 배포: CIPL live read · Foodil 복제 · Quoteapp 직접 Foodil — 소비자(투영)마켓플레이스 회사 상품(master-fed): master 필드 읽기전용 셀러 상품(마스터 외): 지금처럼 Foodil 관리 로컬 유지: 주문 · 스토어 · 가격정책 · 임베딩 · RLS Quoteapp — 소비자(투영)내부 ERP · 신규 Postgres 카탈로그 전체가 master-fed (회사 상품) JSON 파일 → 정식 DB로 전환 로컬 유지: 견적 · PO · 원장 · 환율 문서 생성 시점 export CIPL — 문서 서비스 Quoteapp export(buildCiplPayload)로 CI/PL 생성 동기화 대상 아님 · 외부 고객 카탈로그는 범위 밖
마스터(허브) Foodil(소비자) Quoteapp(소비자) CIPL(문서 서비스) 단방향 동기화
DIAGRAM 02

배포 방식 — DB-native (live read · 복제), bespoke 이벤트 없음

소비자마다 실제 필요에 맞는 가장 단순·견고한 방식으로 배포합니다 — CIPL은 문서 시점 live read(항상 최신), Foodillogical replication으로 읽기전용 사실 미러 + 로컬 overlay JOIN, Quoteapp(어드민)은 core를 직접 read/write. outbox·pg_net 같은 자체 이벤트 파이프라인을 만들지 않습니다.

core 마스터 (전용 DB)Quoteapp 시드 · 사실의 단일 진실 products · brands · categories product_prices (원가·기준가) product_sources (계보·시드) 작성: 얇은 전용 어드민 (단일 작성 지점) 하드닝된 DB · 백업 · 격리 (프로토타입 아님) 직접 read/write (작성) Quoteapp — 어드민 (core 직접)상품 작성/편집 · 동기화 불필요 문서 시점 live read CIPL — 문서 시점 live read캐시 아님 → 통관·결제 문서 항상 최신 logical replication Foodil — 복제 미러 + 로컬 overlay 사실 미러 (읽기전용 · 복제) product_overlay (로컬·편집) 상품 뷰 = 사실 미러 JOIN overlay · FK·주문·RLS 로컬 유지 ✗ 자체 outbox / pg_net 이벤트 파이프라인 없음 모두 DB 네이티브 — live read · logical replication · 직접 접근 (트랜잭션·관측 가능, 운영 부담 최소)
core 마스터(전용 DB) Quoteapp 어드민(직접) CIPL(live read) Foodil(복제+overlay)
DIAGRAM 03

일회성 시드 & 점진 롤아웃 — 한 번 정합 후 동기화

먼저 회사 카탈로그를 1회 정합(Foodil + Quoteapp 중복 제거)해 마스터를 채우고, master_product_id를 역기록한 뒤 동기화를 켭니다. 이후 작성은 어드민에서만. 롤아웃은 Quoteapp 먼저(thin slice) → Foodil 순으로 점진 확장합니다.

1단계 · 일회성 정합 시드 (중복을 1회만 제거) Foodil회사 상품 추출 QuoteappJSON 카탈로그 정합 · 정제매칭 · 빈이름 격리 · 통화정규화 core 마스터단일 진실 master_product_id소비자에 역기록 정합 완료 → 동기화 ON, 이후 작성은 어드민에서만 2단계 · 점진 롤아웃 (thin slice → 확장 · 빅뱅 없음) ① Quoteapp 먼저DB 없는 내부 도구 →위험 최소 · 즉시 효과 ② Foodil 소비자 연결회사 상품 투영 수신 +master 필드 편집 잠금 ③ 루프 검증작성→동기화→소비엔드투엔드 확인 ④ 카탈로그 확장전체로 스케일 ·replication 안정화
Foodil Quoteapp 마스터 검증 게이트 단방향 동기화
필드 매핑 · Field Mapping

회사 상품 필드 → 상품 마스터(core)

일회성 정합 시 각 시스템의 필드가 마스터의 어디로 가는지입니다. 검토에서 검증된 충돌(단위 vs 카톤 중량, 통화 부재, 바코드 유형, 규격 원문)은 모두 해소했습니다. 동기화 시 마스터 소유 필드만 소비자로 갱신됩니다.

개념FoodilQuoteapp마스터(core)소유
상품 IDproduct_id bigintid 'prod-…'product_id uuid + sources마스터
품번/SKUitemNoitem_no마스터
브랜드brand_id (필수)brand 문자열brand_id + brand_text마스터
상품명nameproduct (805건 공란→격리)name (nullable)마스터
규격 원문contentspecspec_raw (원문 보존)마스터
단위/카톤 중량net_weight·grossnetWeightKg·grossWeightKgnet_weight_per_unit · *_carton_kg (분리)마스터
CBM·카톤크기carton_*_mm·cbmcartonSize 문자열carton_*_mm·cbm (파싱)마스터
바코드barcode (단일)pcs/bundle/cartonbarcode_piece/bundle/carton마스터
HS codehs_codehsCodehs_code (nullable)마스터
원가·기준가prices 통화 없음KRW/USD 원가·판매가product_prices 통화+kind 명시마스터
공개여부·스토어is_public · 스토어없음(마스터에 두지 않음)Foodil 로컬
마켓 가격정책커미션·바이어별(마스터에 두지 않음)Foodil 로컬
견적 라인·원장quotes.items·ledger(마스터에 두지 않음)Quoteapp 로컬
전체 로드맵 · Roadmap

전체 계획 — 검증에서 정착까지

한 번에 전부 만들지 않습니다. 검증(P0)으로 시작해 마스터+thin slice(P1)로 루프를 증명하고, Foodil 소비자(P2), 전체 확장+강화(P3)를 거쳐 정착·거버넌스(P4)에서 “3중 관리”를 완전히 종료합니다. 각 단계는 완료 기준을 통과해야 다음으로 넘어갑니다.

DIAGRAM 04

단계 타임라인 — 게이트로 구분된 점진 진행

왼쪽에서 오른쪽으로 진행합니다. 앞 단계의 완료 기준을 통과해야 다음 단계로 넘어가며, 최종 목표는 한 번만 관리(드리프트 0)입니다.

PHASE 0 검증 & 기반 앱 코드 검증 ·열린 결정 확정 PHASE 1 마스터 + thin slice 어드민 + Quoteapp ·루프 검증 PHASE 2 Foodil 소비자 투영 전환 ·master 필드 잠금 PHASE 3 전체 + 강화 전체 카탈로그 ·관측성 · 복제 PHASE 4 정착 & 거버넌스 구 편집 폐기 ·드리프트 0
검증 게이트 마스터 구축 Foodil 전환 확장·강화 목표: 한 번만 관리
PHASE 0
검증 & 기반

빌드 전 가정을 실제 앱 코드로 검증하고 열린 결정을 확정한다. 이 패키지엔 앱 소스가 없어 실제 레포 확인이 필요한 단계.

산출
  • Foodil 상품 쓰기 경로 식별 — 잠금 대상 RPC/화면 · 회사 vs 셀러 상품 구분 기준
  • Quoteapp→CIPL export 형태 확인 — CIPL이 문서 시점에만 소비함을 확정
  • Foodil/Quoteapp 데이터량 · 회사 상품 비중 파악
  • 열린 결정 확정 — 카탈로그 속성 위치 · DB 쓰기 가드 방식 · 마스터 어드민 인증/스태프
완료 기준 — 소유 매트릭스 · 잠금 대상 · 범위가 코드로 확정됨
PHASE 1
마스터 + thin slice (Quoteapp)

소량으로 작성→동기화→소비 루프를 증명한다. Quoteapp은 DB가 없어 위험이 가장 낮고 효과가 즉시 보이는 첫 대상.

산출
  • core 마스터 스키마 (전용 하드닝 DB · 기존 스키마 트림: products·brands·categories·prices·sources)
  • 마스터 어드민 최소 CRUD — 단일 작성 지점
  • 일회성 시드(소량) — Foodil+Quoteapp 정합 · 빈 이름 격리 · 통화 정규화
  • Quoteapp 정식 Postgres 전환 + 마스터 투영 소비 (JSON 편집 종료)
완료 기준 — 어드민 변경이 Quoteapp에 반영되는 엔드투엔드 루프 통과
PHASE 2
Foodil 소비자

Foodil 회사 상품을 마스터 투영으로 전환하고 중복 편집을 차단한다. 셀러 상품은 그대로.

산출
  • Foodil에 master_product_id + logical replication 사실 미러 + product_overlay(로컬) 테이블
  • CIPL live-read 경로 확인 · 자체 이벤트 파이프라인 없음
  • master-fed 행의 master 필드 편집 잠금 (UI + DB 가드) · 셀러 상품 불변
  • 회사 상품 전체에 master_product_id 역기록
완료 기준 — Foodil 회사 상품이 마스터로만 갱신 · 중복 편집 경로 차단
PHASE 3
전체 카탈로그 + 동기화 강화

thin slice를 전체 카탈로그로 확장하고 운영을 견고화한다.

산출
  • 전체 시드 정합 — 회사 카탈로그 전량 (사람 검토 병행)
  • 배포 견고화 — replication 지연 모니터링 · live-read 경로 · 멱등
  • 관측성 — 동기화 지연 · 드리프트 알림 · 실패 큐(DLQ) · 재시도
  • 멱등 · 커서 견고화
완료 기준 — 전체 회사 카탈로그 동기화 · 지연 SLA 충족 · 드리프트 0
PHASE 4 · 목표 ★
정착 & 거버넌스

“3중 관리”를 완전히 종료하고 지속 운영 체계를 둔다. 단일 작성 지점이 정착되는 단계.

산출
  • 구 상품 편집 경로 완전 폐기 (Foodil·Quoteapp)
  • CIPL export가 마스터 기반 데이터로 동작함을 검증
  • 거버넌스 — 마스터 소유권 · 변경 프로세스 · 데이터 품질 규칙 · 정기 dedup 리뷰
  • 이후 확장(옵션) — 분석 read-model · 추가 소비자 · 공급사 정합
완료 기준 — 단일 작성 지점 정착 · 카탈로그 드리프트 0 · “한 번만 관리” 달성
앱 연결 & 다음 단계

기존 앱이 마스터에 붙는 방식, 그리고 그 다음

기존 앱 → 마스터 연결

  • Quoteapp — JSON 파일 → 정식 Postgres. 카탈로그 전체를 마스터 투영으로 수신. 견적/PO/원장은 로컬.
  • Foodilproductsmaster_product_id 추가. 회사 상품은 투영으로 갱신·편집 잠금, 셀러 상품은 그대로.
  • CIPL — 무변경. Quoteapp export로 문서 생성 시점에만 데이터 수신.
  • 작성 권한 — 상품 작성/편집은 마스터 어드민에서만. 이게 “3중 관리”를 실제로 끝냅니다.
  • 로컬 보존 — 각 앱의 주문·문서·스냅샷·RLS·FK는 그대로 유지.

현재 위치 & 확인 필요

설계 스펙 작성 완료(MDM-DESIGN.md). 현재 위치는 Phase 0(검증) — 전체 일정은 위 로드맵 참조.

  • Phase 0 검증 — 실제 앱 코드로 가정 확인(아래 항목).
  • Phase 1 thin slice — 마스터 스키마 + 어드민 + Quoteapp 투영.
  • Phase 2~4 — Foodil 소비자 → 전체 확장 → 정착(로드맵).

확인 필요 (열린 항목)

  • 앱 코드 검증 — Foodil 상품 쓰기 경로(잠금 대상) · Quoteapp→CIPL export 형태.
  • 마켓 카탈로그 속성 위치 — storage_type·service_areas는 마스터 vs Foodil 로컬?
  • DB 레벨 쓰기 가드 · 마스터 어드민 인증 — UI 잠금 외 트리거 가드, 스태프 인증.