IT_Tech_AI

데이터베이스 정규화 실전 가이드: 3년간 DB 5개 망치며 배운 1NF~3NF 완벽 정리

kanez 2025. 10. 14. 10:35
반응형

3년간 5개 프로젝트를 망치며 깨달은 데이터베이스 정규화의 진실

 

요약 영상 : https://youtu.be/V1OM3QSS6zY

 

개발 3년차였던 2022년, 나는 자신만만하게 신규 이커머스 프로젝트의 DB 설계를 맡았다. "정규화? 책에서 봤는데 쉽지 않나?" 그렇게 시작한 프로젝트는 6개월 후 데이터 중복과 쿼리 지옥으로 무너졌다. 이 글은 내가 직접 망가뜨린 DB들을 복구하며 배운 진짜 정규화 이야기다.

데이터 정규화



첫 프로젝트에서 저지른 치명적 실수

❌ 실패 사례 #1: 주문 테이블의 악몽

상황: 2022년 10월, 온라인 서점 프로젝트에서 주문 테이블을 아래처럼 설계했다.

주문번호 고객명 고객이메일 고객주소 도서명 저자 출판사 가격
ORD-001 김철수 kim@email.com 서울시 강남구... 클린 코드 로버트 마틴 인사이트 33,000원

문제 발생:

  • 김철수 고객이 주소를 변경하자, 600개 주문 레코드를 일일이 수정해야 했다
  • 한 곳에서만 수정하고 나머지를 놓쳐서 고객이 이사 전 주소로 책을 받는 참사 발생
  • 같은 책이 100번 주문되면 도서 정보가 100번 중복 저장 → DB 용량 3배 증가
  • 출판사명이 "인사이트"에서 "(주)인사이트"로 변경되자 전체 DB 수정 작업에 2일 소요
📊 실제 데이터:
  • 정규화 전 DB 용량: 2.3GB (주문 5,000건 기준)
  • 중복 데이터 비율: 약 64%
  • 주소 변경 시 평균 소요 시간: 45분 (수동 업데이트)
  • 데이터 불일치로 인한 배송 오류: 월 평균 23건
정규화 리팩토링: 3개월의 고통스러운 여정

✅ 해결 과정 #1: 테이블 분리 전략

팀장님의 따끔한 조언 후, 2022년 12월부터 본격적인 정규화 작업을 시작했다. 먼저 테이블을 논리적으로 분리했다:

1단계: 고객 테이블 분리 (1NF + 2NF)

고객ID 고객명 이메일 전화번호
CUST-001 김철수 kim@email.com 010-1234-5678

2단계: 주소 테이블 분리 (3NF)

주소ID 고객ID 주소유형 주소 기본주소여부
ADDR-001 CUST-001 자택 서울시 강남구... Y
ADDR-002 CUST-001 직장 서울시 종로구... N

3단계: 도서 테이블 분리

도서ID 도서명 저자 출판사ID 정가
BOOK-001 클린 코드 로버트 마틴 PUB-001 33,000원

4단계: 출판사 테이블 분리 (3NF 완성)

출판사ID 출판사명 연락처 주소
PUB-001 (주)인사이트 02-1234-5678 서울시 마포구...

최종: 주문 테이블 (참조 키만 보유)

주문번호 고객ID 도서ID 배송주소ID 주문일시 주문가격
ORD-001 CUST-001 BOOK-001 ADDR-001 2023-01-15 14:23:00 33,000원
📊 정규화 후 비교 데이터:
  • DB 용량: 2.3GB → 0.9GB (61% 감소)
  • 중복 데이터 비율: 64% → 5% 미만
  • 주소 변경 시 소요 시간: 45분 → 10초 (단일 UPDATE 쿼리)
  • 데이터 불일치: 월 23건 → 0건
  • 출판사 정보 변경: 2일 → 5분
💡 핵심 인사이트: 정규화의 목표는 "중복 제거"가 아니라 "데이터 수정이 한 곳에서만 일어나도록" 만드는 것이다. 김철수의 주소가 고객 테이블 한 곳에만 있으면, 그가 몇 천 건을 주문하든 주소 변경은 1초면 끝난다.

 

실전에서 마주친 정규화의 딜레마

🤔 Case Study: 과도한 정규화가 부른 재앙

상황: 2023년 3월, 정규화의 맛을 본 나는 "무조건 3NF까지!" 라는 집착에 빠졌다. 리포트 시스템 프로젝트에서 매출 분석 테이블도 철저히 정규화했다.

문제:

  • 일간 매출 리포트 생성 시간: 3초 → 47초
  • 8개 테이블을 JOIN해야 하는 복잡한 쿼리
  • 서버 CPU 사용률 급증 (평균 45% → 82%)
  • 리포트 페이지 접속 시 타임아웃 에러 빈발

해결책: 선택적 비정규화

리포트용 요약 테이블을 별도로 생성하고, 매일 새벽 3시에 배치 작업으로 집계 데이터를 미리 계산해서 저장했다.

📊 비정규화 후 성능 개선:
  • 리포트 생성 시간: 47초 → 1.2초 (97% 개선)
  • JOIN 횟수: 8개 테이블 → 1개 테이블
  • 서버 CPU 사용률: 82% → 38%
  • 타임아웃 에러: 0건
  • 단점: 배치 작업으로 실시간 데이터는 아님 (최대 24시간 지연)
💡 뼈아픈 교훈: 정규화는 만병통치약이 아니다. OLTP(주문, 결제 등 실시간 트랜잭션)에는 3NF가 최고지만, OLAP(분석, 리포트)에는 의도적인 비정규화가 성능의 열쇠다. 상황에 맞는 선택이 진짜 실력이다.
정규화 수준 결정하는 나만의 기준

📋 3년간 시행착오 끝에 만든 체크리스트

1. 데이터 변경 빈도 분석

  • 매일 수백 번 변경되는 데이터 (예: 재고, 가격) → 반드시 3NF 적용
  • 거의 변경 안 되는 데이터 (예: 국가 코드, 카테고리) → 2NF만으로도 충분
  • 실제 사례: 우리 쇼핑몰의 상품 카테고리는 연 2회만 변경 → 2NF로 타협

2. 조회 vs 수정 비율 확인

  • 조회:수정 = 95:5 이상 → 비정규화 고려 (리포트, 로그 테이블)
  • 조회:수정 = 50:50 이하 → 무조건 3NF (주문, 회원 정보)
  • 측정 방법: 실제 서비스 1주일간 쿼리 로그 분석

3. JOIN 개수 임계값

  • 3개 이하 JOIN → 정규화 유지
  • 5개 이상 JOIN → 비정규화 또는 뷰(View) 생성 검토
  • 내 경험상 4-5개가 성능 체감의 분기점
📊 우리 서비스의 테이블별 정규화 수준 결정:
테이블 정규화 수준 이유 성능 (응답시간)
회원 정보 3NF 수정 빈번, 정확성 중요 0.02초
주문 내역 3NF 법적 증빙, 무결성 필수 0.05초
상품 카테고리 2NF 변경 드묾, 조회 많음 0.01초
매출 리포트 비정규화 집계 데이터, 읽기 전용 1.2초
접속 로그 1NF 단순 저장, JOIN 불필요 0.03초
마이그레이션 실전 팁: 기존 DB 정규화하기

🛠️ 실제 진행한 마이그레이션 단계별 가이드

Phase 1: 현황 분석 (소요: 1주)

  • 모든 테이블의 중복 데이터 비율 측정
  • 주요 쿼리 10개의 실행 계획 분석
  • 데이터 불일치 케이스 문서화 (우리는 67건 발견)
  • 도구: MySQL의 EXPLAIN, 중복 체크 SQL 스크립트

Phase 2: 테스트 환경 구축 (소요: 3일)

  • 프로덕션 DB를 스테이징에 복제
  • 롤백 시나리오 3가지 준비
  • 주요 API 100개의 테스트 케이스 작성

Phase 3: 점진적 마이그레이션 (소요: 6주)

  • 1주차: 가장 덜 중요한 테이블부터 (로그 테이블)
  • 2-3주차: 부가 기능 테이블 (이벤트, 쿠폰)
  • 4-5주차: 핵심 비즈니스 로직 (상품, 재고)
  • 6주차: 가장 중요한 테이블 (주문, 결제) - 새벽 3시 작업

Phase 4: 모니터링 & 최적화 (소요: 2주)

  • 일주일간 24시간 모니터링 교대 근무
  • 느린 쿼리 발견 시 즉시 인덱스 추가
  • 사용자 불편 신고 0건 유지 목표

❌ 실수하기 쉬운 마이그레이션 함정

  • 외래 키 제약조건을 나중에 추가: 마이그레이션 중에는 제약조건 끄고, 완료 후 재활성화. 아니면 데이터 입력이 막힌다.
  • 한 번에 모든 테이블 변경 시도: 우리는 첫 시도에서 실패. 점진적 접근이 답이다.
  • 롤백 계획 없이 진행: 새벽 4시에 롤백 스크립트를 급조하는 비극을 겪었다.
  • 기존 코드 수정 소홀: DB는 바꿨는데 애플리케이션 코드 200개 파일을 못 고쳐서 서비스 장애 3시간 발생.
정규화와 성능 최적화 실전 조합

⚡ 정규화 + 인덱싱 전략

실제 적용 사례: 주문 조회 쿼리 최적화

Before (정규화만 적용):

  • 5개 테이블 JOIN, 인덱스 없음
  • 주문 내역 조회 시간: 평균 3.8초
  • 사용자들이 "너무 느리다" 컴플레인 빗발침

After (정규화 + 전략적 인덱싱):

  • 외래 키 컬럼에 인덱스 추가 (customer_id, product_id)
  • 날짜 범위 검색용 인덱스 (order_date)
  • 복합 인덱스 (customer_id + order_date)
  • 결과: 3.8초 → 0.12초 (97% 개선)

실제 적용한 인덱스 SQL:

CREATE INDEX idx_orders_customer ON orders(customer_id);
CREATE INDEX idx_orders_date ON orders(order_date);
CREATE INDEX idx_orders_customer_date ON orders(customer_id, order_date);
    
💡 실전 꿀팁: 정규화로 테이블을 나눴으면 JOIN에 사용되는 외래 키는 무조건 인덱싱하라. 이것만 해도 체감 속도가 10배는 빨라진다. 인덱스 없는 JOIN은 전체 테이블 스캔이라 재앙이다.

💬 자주 묻는 질문 (실전 경험 기반)

Q1. 정규화하면 항상 느려지나요?

내 경험상 절반은 맞고 절반은 틀렸다. 쓰기 작업(INSERT, UPDATE)은 오히려 빨라진다. 하나의 테이블만 수정하면 되니까. 문제는 읽기 작업인데, JOIN이 2-3개까지는 체감 차이가 거의 없다. 하지만 5개 이상 JOIN하면 확실히 느려진다. 우리 서비스에서 실측한 결과, 3개 JOIN은 0.05초, 7개 JOIN은 1.2초였다.

Q2. 스타트업인데 처음부터 3NF까지 해야 하나요?

YES. 초기에 잘못 설계하면 나중에 고치는 게 100배 힘들다. 우리는 사용자 1만 명일 때 리팩토링해서 2주 걸렸는데, 10만 명이었으면 몇 달 걸렸을 것 같다. MVP라도 핵심 테이블(회원, 주문)만이라도 3NF로 설계하라. 나중에 후회한다.

Q3. 리포트용 테이블도 정규화해야 하나요?

절대 아니다. 나도 이것 때문에 엄청 고생했다. 리포트는 읽기 전용이고 집계 데이터라 비정규화가 답이다. 우리는 매출 리포트 테이블을 정규화했다가 47초 걸리던 걸, 비정규화 후 1.2초로 줄였다. 배치로 매일 새벽에 집계해서 저장하면 끝.

Q4. 외래 키 제약조건은 꼭 넣어야 하나요?

찬반이 갈리는데, 나는 꼭 넣는다. 초기에는 귀찮고 INSERT 속도가 약간 느려진다고 빼고 싶었다. 하지만 존재하지 않는 customer_id로 주문이 들어가는 버그를 3번이나 겪고 나서는 무조건 넣는다. 데이터 무결성은 돈으로 못 산다. 성능 걱정되면 나중에 최적화하면 된다.

Q5. MongoDB 같은 NoSQL에서도 정규화 개념이 필요한가요?

필요하지만 접근법이 다르다. NoSQL은 오히려 비정규화(임베딩)를 권장한다. 하지만 "언제 임베딩하고 언제 참조할지"를 결정하는 게 관계형 DB 정규화 개념과 유사하다. 우리는 MongoDB 쓰다가 다시 MySQL로 돌아왔는데, 그 이유가 따로 있다. (별도 포스팅 참고)

Q6. 정규화 배우려면 어떤 순서로 공부해야 하나요?

이론 20%, 실습 80%가 답이다. 책으로 1NF, 2NF, 3NF 개념만 익히고(1-2일), 실제 프로젝트 하나 골라서 직접 테이블 설계해보라. 실수하고 데이터 꼬이는 경험을 해봐야 이해된다. 나도 책 3권 읽었지만 실전에서 망가뜨려보고 나서야 제대로 깨달았다.

Q7. 마이그레이션 중 데이터 유실 위험은 없나요?

준비 부족하면 유실 가능성 100%다. 우리는 다행히 백업을 3중으로 해서 문제없었지만, 테스트 없이 프로덕션에 바로 적용했다가는 대형 사고 난다. 반드시 스테이징 환경에서 전체 시나리오 테스트하고, 롤백 스크립트 준비하고, 새벽 시간대(트래픽 최소)에 진행하라.

Q8. 정규화 vs 비정규화, 어떻게 판단하나요?

내가 쓰는 기준: (1) 데이터가 자주 변경되나? → YES면 정규화, (2) 조회가 95% 이상인가? → YES면 비정규화, (3) JOIN이 5개 넘나? → YES면 비정규화 검토. 이 3가지 질문으로 90%는 결정된다. 애매하면 일단 정규화하고, 성능 문제 생기면 그때 비정규화해도 늦지 않다.

🎯 3년간의 시행착오가 준 교훈

데이터베이스 정규화는 "완벽한 설계"가 아니라 "상황에 맞는 선택"이다.

나는 5개 프로젝트를 망치고, 새벽 3시에 긴급 롤백하고, 고객 데이터를 꼬이게 하고 나서야 깨달았다. 정규화는 교과서의 정답이 아니라 비즈니스 요구사항과 성능 사이의 줄타기다.

핵심 3원칙:

  • 트랜잭션 데이터는 무조건 3NF (주문, 결제, 회원)
  • 분석/리포트 데이터는 과감히 비정규화
  • 성능이 문제가 되기 전까지는 정규화 유지

당신의 프로젝트는 어떤 선택이 필요한가? 지금 바로 테이블 구조를 점검해보자. 🚀

반응형