테마
정규화와 반정규화 3
정규화(normalization)는 중복을 없애 데이터를 바르게 지키지만, 테이블이 잘게 나뉘는 만큼 읽을 때 여러 테이블을 키로 이어 읽는 조인(join)이 늘어난다. 반정규화(denormalization)는 정규화한 설계에서 읽기 성능을 위해 일부러 중복을 되살리는 결정이다. 먼저 정규형(normal form)을 한 표로 정리하고, 반정규화를 언제 어떻게 하며 무엇을 대가로 내는지 본다.
정규형 한눈에
아래 표의 1NF·2NF·3NF는 제1·제2·제3정규형(First·Second·Third Normal Form)이고, BCNF(Boyce-Codd Normal Form, 보이스-코드 정규형)는 제3정규형을 더 엄격하게 만든 단계다. 비주 속성(non-prime attribute)은 어느 후보 키(candidate key)에도 속하지 않는 칼럼이다. 함수 종속(functional dependency) X → Y는 X 값이 정해지면 Y 값이 하나로 정해지는 관계이고, 값을 정하는 쪽 X를 결정자(determinant)라 한다. 슈퍼 키(super key)는 행을 하나로 구분할 수 있는 칼럼 묶음이다. 2NF와 3NF는 키의 일부에만 종속하는 부분 함수 종속(partial dependency)과 키가 아닌 칼럼을 거쳐 종속하는 이행 함수 종속(transitive dependency)을 걷어 낸다. 4NF와 5NF가 다루는 다치 종속(multivalued dependency)과 조인 종속(join dependency)은 표 아래에서 설명한다.
| 정규형 | 걷어 내는 것 | 한 줄 기준 |
|---|---|---|
| 1NF | 목록·반복 칼럼 | 칸마다 원자값(atomic value) 하나 |
| 2NF | 부분 함수 종속 | 비주 속성은 키 전체에 종속 |
| 3NF | 이행 함수 종속 | 비주 속성은 키에만 종속 |
| BCNF | 슈퍼 키가 아닌 결정자 | 모든 결정자가 슈퍼 키 |
| 4NF | 다치 종속 | 서로 무관한 값 목록을 한 테이블에 두지 않음 |
| 5NF | 조인 종속 | 후보 키로 설명되지 않는 조인 종속이 없음 |
4NF 이상은 실무 설계에서 드물게 따진다. 다치 종속은 한 학생의 취미 목록과 수강 과목 목록처럼 서로 관계없는 두 값 목록이 한 테이블에 같이 들어, 조합 수만큼 행이 불어나는 경우다. 목록마다 테이블을 따로 두면 풀린다. 조인 종속은 테이블을 몇 개로 나눴다가 다시 조인하면 원래 테이블이 정확히 되살아나는 관계다. 이렇게 행이 빠지지도 없던 행이 생기지도 않게 나누는 것을 무손실 분해(lossless decomposition)라 한다. 어떤 테이블은 둘로 나누면 조인했을 때 없던 행이 생기지만, 셋으로 나누면 정확히 되살아난다. 5NF는 이런 경우까지 모두 나눠, 후보 키에서 저절로 따라 나오는 조인 종속만 남은 상태다.
정보처리기사 시험에서는 단계별로 걷어 내는 대상의 앞 글자를 따서 "도부이결다조"(도메인 원자값, 부분 함수 종속, 이행 함수 종속, 결정자, 다치 종속, 조인 종속)로 외운다. 도메인은 칼럼에 들어갈 수 있는 값의 범위이고, '도메인 원자값'은 그 값이 모두 원자값이어야 한다는 1NF 조건이다.
반정규화는 왜 하나
제3정규형까지 나눈 수강 설계에서 "학생 이름, 과목명, 성적" 목록을 보여 주려면 학생·과목·수강 세 테이블을 조인해야 한다. 목록 화면이 초당 수천 번 불리고 행이 수억 개라면, 매번 조인하고 집계하는 비용이 쌓인다. 이때 수강 테이블에 과목명을 함께 적어 두면 조인 하나가 빠진다. 이것이 반정규화다.
대표적인 기법은 다음과 같다.
- 중복 칼럼 추가: 자주 함께 읽는 다른 테이블의 칼럼을 복사해 둔다(수강에 과목명).
- 파생 칼럼 추가: 매번 계산하던 값을 저장해 둔다(게시글에 댓글 수, 주문에 합계 금액).
- 요약 테이블: 일별·월별 집계를 미리 만들어 둔 테이블을 따로 둔다.
- 테이블 합치기·나누기: 한쪽 행 하나에 다른 쪽 행이 하나씩만 대응하는(1:1) 두 테이블을 늘 같이 읽으면 합치거나, 자주 읽는 칼럼과 가끔 읽는 큰 칼럼을 나눈다.
대가와 판단 순서
반정규화의 대가는 정규화가 막아 주던 문제 그대로다. 같은 사실이 두 곳에 있으므로 원본이 바뀔 때 복사본도 함께 고쳐야 하고, 하나라도 놓치면 갱신 이상(update anomaly)이 다시 생긴다. 쓰기 작업이 늘고 저장 공간도 더 든다. 그리고 복사본을 맞출 책임이 DB 제약에서 애플리케이션 코드, 트리거(trigger), 배치 작업 같은 사람이 관리하는 장치로 옮겨 간다.
그래서 순서가 중요하다.
- 먼저 제3정규형까지 정규화한다.
- 실제로 느린 쿼리를 측정해 찾는다.
- 인덱스, 쿼리 수정, 캐시처럼 중복을 만들지 않는 방법을 먼저 쓴다.
- 그래도 부족할 때 해당 화면에 필요한 만큼만 반정규화하고, 복사본을 누가 언제 맞출지 정한다.
반정규화처럼 보이지만 사실은 올바른 모델링인 경우도 있다. 주문 상세에 적는 "주문 당시 가격"은 상품 테이블의 현재 가격을 복사한 중복이 아니다. 나중에 상품 가격이 바뀌어도 그대로 남아야 하는 다른 사실이다. 이런 값은 원본과 맞출 필요가 없으므로 반정규화의 대가도 없다.
주문·결제처럼 짧은 쓰기가 많은 OLTP(Online Transaction Processing, 온라인 트랜잭션 처리) 테이블은 정규화 쪽에 무게를 둔다. 쓰기가 잦은 곳에 중복이 있으면 쓸 때마다 복사본까지 고쳐야 해 쓰기 비용과 갱신 이상 위험이 함께 커지기 때문이다. 대량 데이터를 집계하는 OLAP(Online Analytical Processing, 온라인 분석 처리)용 분석 DB는 읽기가 대부분이라 처음부터 반정규화된 구조로 만드는 경우가 많다.
면접 포인트
- 정규화 = 중복 제거로 무결성, 반정규화 = 읽기 성능을 위해 중복을 되살림. 둘은 트레이드오프.
- 기법: 중복 칼럼, 파생 칼럼(카운트·합계), 요약 테이블, 테이블 합치기·나누기.
- 대가: 갱신 이상 재발, 쓰기 비용·공간 증가, 동기화 책임이 코드·트리거·배치로 이동.
- 순서: 3NF까지 정규화 → 측정 → 인덱스·쿼리·캐시 → 그래도 안 되면 필요한 만큼만.
- "주문 당시 가격"은 반정규화가 아니라 별개의 사실.
