반정규화: 성능을 위한 의도적 중복
정규화된 모델을 출발점으로 반정규화의 적용 조건·기법·무결성 비용을 판단한다.
핵심 요약
정규화된 모델을 출발점으로 반정규화의 적용 조건·기법·무결성 비용을 판단한다.
핵심 질문
- 반정규화: 성능을 위한 의도적 중복를 실제 업무 사례에서 어떤 기준으로 판별하는가?
- 한 행이 나타내는 업무 사실과 유일성·필수성·변경 가능성은 무엇인가?
- 잘못 모델링하면 어떤 중복·이상 현상·변경 영향이 발생하는가?
- 여러 설계안 중 업무 의미와 변경 용이성을 가장 잘 보존하는 안을 어떻게 고르는가?
학습 목표
- 반정규화가 정규화의 반대 작업만을 뜻하지 않는 이유를 설명한다.
- 테이블·컬럼·관계 수준의 반정규화 기법을 사례에 적용한다.
개념 지도
후보키·함수 종속성 → 이상 현상 → 정규형 분해 → 성능 측정 → 제한적 반정규화
핵심 내용
반정규화는 조회 성능이나 운영 단순화를 위해 의도적으로 중복을 허용하거나 구조를 조정하는 설계다. 먼저 정규화된 모델과 실제 SQL, 데이터량, 변경 빈도를 확인한 뒤 적용한다.
- 테이블: 병합, 분할, 요약·집계 테이블 추가
- 컬럼: 중복 컬럼, 파생 컬럼, 이전·현재 상태값 저장
- 관계: 잦은 조인을 줄이기 위한 중복 관계 추가
예를 들어 주문 목록에서 고객명을 매우 자주 조회한다고 주문에 고객명을 복제하면 조인은 줄지만, 고객명 변경 시 모든 주문의 값을 맞춰야 한다. 따라서 갱신 주체, 동기화 방식, 검증 절차가 설계에 포함되어야 한다.
적용 순서
- 병목 SQL과 업무 요구를 측정한다.
- 인덱스·SQL 개선·캐시 등 다른 방법을 검토한다.
- 읽기 이득과 쓰기·무결성 비용을 비교한다.
- 적용 후 성능과 불일치를 감시한다.
흔한 오해와 주의점
- 반정규화는 데이터 무결성을 포기하는 작업이 아니다.
- 정규화가 항상 느리고 반정규화가 항상 빠른 것은 아니다.
- 컬럼 중복은 저장 공간뿐 아니라 변경 경로와 잠금 범위도 늘릴 수 있다.
문항 풀이 보강: 반정규화 기법 선택
반정규화는 단순히 테이블을 합치는 작업이 아니다.
| 수준 | 대표 기법 | 주된 이득 | 함께 드는 비용 |
|---|---|---|---|
| 테이블 | 병합, 수직·수평 분할, 통계·이력·요약 테이블 | 조인·스캔 범위 감소 | 동기화·추가 저장 |
| 컬럼 | 중복 컬럼, 파생 컬럼, PK 컬럼의 일반 컬럼 복제 | 반복 조인·계산 감소 | 갱신 경로 증가 |
| 관계 | 중복 관계 추가 | 긴 조인 경로 단축 | 참조 무결성 관리 |
자주 사용하는 소수 컬럼과 거의 사용하지 않는 큰 컬럼이 한 넓은 테이블에 섞여 블록당 행 수가 줄어든다면, 1:1 수직 분할로 핵심 컬럼을 별도 테이블에 둘 수 있다. 행의 종류나 기간별로 읽는 범위가 명확하면 수평 분할을 검토한다.
주문목록을 조회할 때 매번 제품 테이블과 조인하는 비용이 실제 병목이라면 제품명을 주문목록에 중복할 수 있지만, 제품명 변경 시 과거 주문의 이름을 바꿀지 보존할지 업무 규칙부터 정해야 한다.
적용 전 체크
- 정규화를 정확히 수행한다.
- 데이터 용량과 증가량을 산정한다.
- 트랜잭션 유형·빈도·범위를 파악한다.
- 병목 SQL을 측정한다.
- 인덱스나 SQL 개선으로 해결 가능한지 본다.
- 읽기 이득이 쓰기·무결성 비용보다 클 때만 적용한다.
정규화 → 용량 산정 → 트랜잭션 분석 → 반정규화·PK/FK·인덱스 검토의 흐름을 기억한다.
중복 컬럼의 Source of Truth 설계
주문에 고객명을 복제한다고 가정한다.
CUSTOMER.customer_name 현재 고객명 원천
ORDERS.customer_name 주문 당시 이름 Snapshot 또는 현재 이름 Cache
두 컬럼의 의미를 먼저 정해야 한다. 과거 주문서에 주문 당시 이름을 남기는 것이라면 값이 달라도 오류가 아니다. 현재 고객명을 빠르게 보여 주기 위한 Cache라면 고객명 변경 시 동기화가 필요하다.
동기화 방식 비교
| 방식 | 장점 | 위험 |
|---|---|---|
| 동일 Transaction에서 함께 갱신 | 즉시 일관성 | 여러 Table Lock·업무 결합 증가 |
| Trigger | 누락 방지 | 숨은 부하·재귀·오류 추적 어려움 |
| Event/비동기 갱신 | 쓰기 응답 분리 | 일시적 불일치·재처리 필요 |
| Batch 재생성 | 단순·대량 효율 | 최신성 지연 |
| Materialized View | Query Rewrite·관리 기능 | Refresh 비용·제약 |
반정규화된 값마다 원천, 갱신 주체, 허용 지연, 검증 Query, 재생성 절차를 문서화한다.
적용 전후 검증
-- 중복값 불일치 점검 예
SELECT COUNT(*)
FROM orders o
JOIN customer c ON c.customer_id = o.customer_id
WHERE o.cached_customer_name <> c.customer_name
OR (o.cached_customer_name IS NULL AND c.customer_name IS NOT NULL)
OR (o.cached_customer_name IS NOT NULL AND c.customer_name IS NULL);
읽기 SQL의 Join·Buffers가 실제로 줄었는지와 DML 시간·Redo·Lock이 얼마나 늘었는지를 함께 측정한다. 측정 근거와 복구 경로가 없다면 정규화 구조를 유지한다.
사례를 판별하는 순서
- 모델이 표현해야 하는 업무 사실과 행 단위(Grain)를 한 문장으로 정의합니다.
- 각 인스턴스를 유일하게 식별할 수 있는 후보와 변경 가능성을 확인합니다.
- 속성이 어느 사실에 종속되는지와 관계의 필수성·카디널리티를 확인합니다.
- 중복 저장으로 삽입·갱신·삭제 이상이 생기는지 검토합니다.
- 물리 성능을 이유로 구조를 바꿀 때 원천 데이터와 동기화·복구 규칙을 함께 설계합니다.
모델 품질 확인표
- 같은 업무 사실이 여러 곳에 중복 저장되지 않는가?
- 이름과 도메인이 한 가지 의미로 사용되는가?
- PK·FK가 실제 업무 관계와 선택성을 정확히 표현하는가?
- 현재 화면이나 프로세스에 과도하게 종속되지 않는가?
- 정규화와 성능 설계의 이유를 측정 가능한 근거로 설명할 수 있는가?
마지막 점검
- 화면의 입력 항목이나 현재 프로세스를 그대로 엔터티·관계로 옮기지 않습니다.
- 한 행의 업무 사실, 후보 식별자와 함수 종속성을 먼저 확정합니다.
- 중복과 비일관성을 만들면서 조회 편의만 얻는 설계를 정답으로 선택하지 않습니다.
- 반정규화와 물리 설계는 측정된 성능 문제와 동기화·복구 대책이 있을 때 적용합니다.
복습 문제
- 파생 금액을 저장하기 전에 정해야 할 세 가지는?
- 반정규화 판단보다 먼저 확보해야 할 근거는 무엇인가?
- 이 개념을 실제 업무 사례에서 판별할 수 있는가?
- 한 행의 업무 의미와 식별자를 설명할 수 있는가?