3편 — 데이터 모델링 & 중복 처리
Staging → Production 구조로 구축한 안정적인 CRM Sync 아키텍처
1. 들어가며
AI가 아무리 똑똑해도, 데이터가 잘못 저장되면 CRM 전체가 오염됩니다.
따라서 “어디에 어떤 형태로 데이터를 저장할 것인가?”를 결정하는 것은 자동화 시스템의 핵심 설계 요소입니다.
이번 글에서는 실제 프로젝트에서
Google Sheets → Notion으로 이어지는 데이터 흐름을 어떤 기준으로 나누고,
어떤 방식으로 중복·오류를 방지했는지를 자세히 설명합니다.
2. Staging vs Production: DB를 나누면 모든 문제가 풀린다
처음에는 Google Sheets 하나만 두고 AI가 데이터를 계속 덮어쓰는 방식이었습니다.
하지만 이 구조는 몇 가지 심각한 문제를 발생시켰습니다.
🧨 문제 1: 과거 기록이 사라짐
고객이 새로운 문의를 할 때마다 기존 기록이 덮어써져,
“지난달 이 고객이 어떤 불만을 가졌지?”를 알 수 없었습니다.
🧨 문제 2: 승인 전/후 데이터 구분 불가
AI가 작성한 초안과 사람이 승인한 최종 데이터를 분리할 수 없었습니다.
🧨 문제 3: 분석용 데이터가 오염됨
오타/누락/중복이 포함된 데이터가 그대로 Notion이나 분석 로직으로 들어갔습니다.
✔ 해결책: Staging → Production 구조로 명확하게 분리
🏗️ Google Sheets = Staging(초안·검수용 와일드 데이터)
- AI가 처음 만든 “날것(raw)” 데이터 저장
- 더럽고 지저분해도 상관 없음
- 사람이 검수하기 가장 좋은 UI
- 승인 여부(Status)로 다음 단계로 넘김
- 로그(History) 역할도 겸함
🗄️ Notion = Production(최종 CRM DB)
- 반드시 “승인된 데이터”만 들어옴
- AI 분석/조회 로직은 반드시 Notion만 읽음
- 깨끗한 데이터만 존재하는 ‘단일 진실 소스(SSOT)’
이 구조 덕분에
Garbage In → Garbage Out 문제를 원천 차단할 수 있었습니다.
3. Notion 데이터 모델링: Property vs Body 전략
Notion DB에는 크게 두 가지 공간이 있습니다.
① Property
- 최신 상태
- 연락처, 최근 문의, 유형 등 빠르게 확인 가능한 필드
- UI에서 한눈에 확인 가능
② Body(Page Content)
- 모든 과거 기록을 쌓아두는 본문
- 타임스탬프 + 상세 내용 Append
- 히스토리 로그로서의 역할
✨ 왜 이렇게 나눴는가?
| Property에 최신값만 유지 | CRM UI 가독성 최고 |
| Body에 히스토리 누적 | 복잡한 DB 설계 없이 로그 완성 |
| 최신값 + 히스토리 모두 존재 | 분석/조회/고객 대응 전부 유리 |
Notion 하나만으로
Master DB + Log DB 역할을 모두 수행할 수 있게 된 것입니다.
4. 중복 처리: Upsert 구조 설계
자동화 시스템에서 가장 해결하기 어려운 것이 중복 데이터 처리입니다.
예를 들어 동일 고객이
오늘 문의하고
이틀 후에 또 문의할 경우
두 개의 신규 레코드가 생기면 안 됩니다.
그래서 설계한 구조는 아래와 같습니다.
✔ Step 1. Notion에서 Email 기준으로 검색
n8n의 Database Pages → Get Many 노드를 사용하여
Email == 입력된 이메일 조건으로 검색합니다.
문제 발생: Search 노드 정확도 문제
일부 문서에서는 Search 노드를 사용하면 정확한 Email 매칭이 되지 않아
전혀 다른 페이지가 함께 반환되는 문제가 있었습니다.
해결
- Search 노드 제거
- Get Many Database Pages로 통일
- Filter → Build Manually 사용
- Always Output Data 옵션 활성화 (검색 결과가 없어도 빈 배열 반환)
✔ Step 2. 신규/기존 여부 판단
1) 기존 고객(O)
- Property에 최신 문의 내용 업데이트
- Body에 히스토리 추가(Append)
2) 신규 고객(X)
- 신규 페이지 생성(Create Page)
- Property 세팅
- Body에 첫 기록 추가
✔ Step 3. Google Sheets에 ‘처리됨’ 표시
처리가 완료되면
IsSynced 값을 완료로 설정합니다.
이 값이 다음 단계의 핵심 역할을 합니다.
5. 동기화 문제와 해결: IsSynced 전략
Google Sheets의 트리거는 이벤트 기반이 아니라 Polling 기반입니다.
즉,
“행이 바뀌면 즉시 호출”이 아니라
“일정 주기로 시트를 살펴보다가 변화가 있으면” 실행됩니다.
여기서 다음 문제가 발생합니다.
🧨 문제: 승인 1건 변경 → 과거 승인된 100건이 동시에 들어옴
관리자가 1개의 Status를 “승인”으로 바꿨는데
트리거는 이 변화를 정확히 구분하지 못하고
과거 승인된 행까지 모두 한 번에 다시 보내는 상황.
이는 아래 두 가지 문제를 유발했습니다.
- 괜히 100건을 다시 처리하여 성능 낭비 (해결하지 못함)
- Notion DB 중복 업데이트 위험
✔ 해결책: 트리거를 “신호”로만 사용 + 능동적 조회(Active Fetch)
① 트리거는 단순 알림 신호
“데이터 변경이 있었어. 확인해보렴!”
정도의 역할만 담당합니다.
② 실제 처리할 데이터는 n8n이 직접 조회
트리거 뒤에 Get Many Rows 노드를 배치하여
Status == 승인 AND IsSynced == 대기
조건을 만족하는 행만 가져옵니다.
③ Loop로 하나씩 처리
Google Sheets에서 여러 건이 넘어와도
Loop 노드로 안전하게 1건씩 처리합니다.
✨ 결과
- 실제로 처리해야 할 단 1건만 정확히 검출
- 중복 실행 완전 차단
- 성능 + 비용 모두 절감
6. Checkbox vs Dropdown: 데이터 무결성 개선
구글 시트에서 처음에는 IsSynced를 체크박스(Boolean)로 관리했습니다.
하지만 큰 문제가 있었습니다.
🧨 공란(Empty)이 FALSE로 처리됨
즉, 체크박스가 미체크 = 데이터 없음이 아니라
JavaScript에서는 사실상 “FALSE”처럼 보입니다.
하지만 n8n 노드에 따라서 FALSE or empty 로 나옴
이 때문에 필터가 정확히 동작하지 않았습니다.
✔ 해결: dropdown 방식으로 변경
IsSynced 값을
- 대기
- 완료
두 가지 텍스트 값으로 명확히 구분했습니다.
장점
- Null / Empty 문제 완전 제거
- 필터링 정확도 상승
- n8n에서 조건 분기 처리 쉬워짐
7. 전체 흐름 요약 (한 컷)
아래 단계만 보면 전체 Sync 아키텍처가 완성됩니다.
- AI가 초안 작성 → Google Sheets에 저장(Status=검토대기)
- 관리자 승인 → Status=승인
- n8n 트리거는 "변화 있음"만 감지
- n8n이 조건(승인/대기)을 만족하는 행만 직접 조회
- Notion DB에 Upsert (기존 고객은 Update, 신규는 Create)
- Body에 히스토리 Append
- 처리 완료 후 IsSynced=완료로 표시
8. 마무리
3편에서는 데이터 구조 설계와 중복 처리라는
CRM 자동화의 가장 중요한 기반 기술을 다뤘습니다.
- 왜 Staging/Production 구조를 썼는지
- 왜 Property/Body를 나눴는지
- 왜 Upsert가 중요한지
- 왜 Trigger Storm이 발생하는지
- 왜 IsSynced 하나가 모든 걸 해결했는지
이런 의사결정 대부분은 단순 자동화가 아니라
데이터 엔지니어링(Data Engineering) 관점에서 나온 해결책입니다.
다음 4편에서는
이번 프로젝트에서 해결했던 모든 주요 장애물(Troubleshooting)과
정량적 효과(ROI) 분석까지 종합적으로 정리해보겠습니다.