역정규화가 필요한 이유: 성능
정규화의 가장 큰 단점은 테이블이 잘게 분리된다는 점이다. 예를 들어, 사용자의 주문 내역과 각 주문에 포함된 상품명 리스트를 조회하려면 어떻게 해야 할까?
SELECT
o.order_id,
o.ordered_at,
m.member_name,
p.product_name,
oi.order_quantity,
oi.order_price
FROM orders o
JOIN member m ON o.member_id = m.member_id
JOIN order_item oi ON o.order_id = oi.order_id
JOIN product p ON oi.product_id = p.product_id
WHERE m.login_id = 'user123';
- 이처럼 간단한 조회 작업에도 member, orders, order_item, product 무려 4개의 테이블을 JOIN 해야 한다.
- 데이터가 적을 때는 문제가 없지만, 수백만 건의 주문 데이터가 쌓이고 수천 명의 사용자가 동시에 접속하는 쇼핑몰이라면 어떨까?
- 잦은 JOIN 연산은 데이터베이스에 부하를 주어 시스템 전체의 성능을 저하시키는 주범이 될 수 있다.
- 바로 이 조회 성능 문제를 해결하기 위해 등장한 것이 역정규화다.
역정규화(Denormalization)
데이터의 조회 성능을 향상시키기 위해, 의도적으로 데이터 모델의 정규화 원칙을 위반하여 데이터의 중복을 허용하는 프로세스다.
정규화와의 트레이드오프(Trade-off) 관계
역정규화란, 데이터베이스의 성능 향상을 위해 의도적으로 정규화 원칙을 위배하고 데이터의 중복을 허용하는 프로세스를 말한다. 주된 목표는 쿼리 실행 시 발생하는 JOIN의 횟수를 줄여서 조회 성능을 극대화하는 것이다.
정규화가 데이터를 "분해"하는 과정이라면, 역정규화는 필요한 데이터를 다시 "통합"하고 "중복"시키는 과정에 가깝다. 역정규화는 데이터의 일관성과 정합성을 일부 희생하고, 조회 속도를 얻는 일종의 트레이드오프 관계에 있다. 따라서 반드시 신중하게 접근해야 한다.
역정규화를 이해하기 위해서는 정규화와의 트레이드오프 관계를 명확히 알아야 한다.
| 구분 | 정규화 (Normalization) | 역정규화 (Denormalization) |
| 목표 | 데이터의 일관성 및 무결성 확보 | 데이터 조회 성능 향상 |
| 장점 | 데이터 중복 최소화, 쓰기(C/U/D) 성능 유리 | 읽기(SELECT) 성능 향상 (JOIN 감소) |
| 단점 | 읽기 성능 저하 가능성 (JOIN 증가) | 데이터 중복 증가, 쓰기 성능 불리, 데이터 불일치 위험 |
- 역정규화는 읽기 속도를 위해 쓰기 속도와 데이터 일관성을 일부 희생하는 전략적인 선택이다.
- 따라서 아무 때나 적용해서는 안 된다. 반드시 정규화된 모델링을 기본으로 하고, 성능 테스트를 통해 명확한 병목 지점이 확인되었을 때 최후의 수단으로 신중하게 적용해야 한다.
실무에서 사용하는 역정규화 기법
1. 중복 컬럼 추가
가장 흔하게 사용되는 기법이다. JOIN을 줄이기 위해 조회 시 자주 필요한 다른 테이블의 컬럼을 그대로 복사해서 가져오는 것이다.
- 문제 상황: 주문 내역 페이지에서 각 주문 상품의 "상품명"을 보여주어야 한다.
- 이를 위해 매번 order_item과 product 테이블을 JOIN 해야 해서 성능이 저하된다.
- 해결책: order_item 테이블에 "product_name" 컬럼을 추가한다.
-- 기존 order_item 테이블에 product_name 컬럼 추가
ALTER TABLE order_item ADD COLUMN product_name VARCHAR(100);
-- 주문이 들어올 때, product 테이블에서 상품명을 복사해서 저장
INSERT INTO order_item (order_id, product_id, order_price, order_quantity, product_name)
VALUES (1004, 10, 1500000, 1, '노트북'); -- product_name을 직접 저장
- 결과: 이제 상품명을 조회하기 위해 product 테이블을 JOIN 할 필요가 없어진다.
- 주의할 점: 이 방식의 치명적인 단점은 데이터 불일치 가능성이다.
- 만약 product 테이블에서 "노트북"의 이름이 "게이밍 노트북"으로 변경된다면, order_item 테이블에 이미 저장된 "노트북"이라는 이름은 자동으로 바뀌지 않는다.
실무 팁: 주문 당시의 상품명, 가격 등은 "역사적 데이터"로 취급하여 변경하지 않는 것이 비즈니스 규칙상 타당할 수 있다. 이런 경우에는 중복 컬럼 추가가 매우 효과적인 해결책이 된다. 만약 데이터 변경을 전파해야 한다면, 애플리케이션 로직이나 데이터베이스 트리거(Trigger)를 통해 product 이름이 바뀔 때 관련된 모든 order_item을 업데이트해주는 추가적인 개발이 필요하다.
2. 파생 컬럼 추가 (계산된 값의 저장)
조회 시점에 복잡한 계산(SUM, COUNT 등)이 필요하여 부하가 발생하는 경우, 그 계산 결과를 미리 컬럼에 저장해 두는 방식이다.
- 문제 상황: 마케팅 팀에서 VIP 고객을 선정하기 위해 회원별 "총 주문 금액"을 자주 조회한다.
- 이 값을 얻으려면 매번 특정 회원의 모든 "order_item"을 읽어 "order_price"와 "order_quantity"를 곱한 값을 모두 더해야 한다.
- 해결책: member 테이블에 "total_purchase_amount"라는 컬럼을 추가하고, 주문이 완료될 때마다 이 값을 업데이트한다.
-- member 테이블에 총 주문 금액 컬럼 추가
ALTER TABLE member ADD COLUMN total_purchase_amount INT NOT NULL DEFAULT 0;
- 결과: 이제 회원별 총 주문 금액을 조회할 때 복잡한 집계 쿼리 없이 member 테이블에서 바로 값을 읽을 수 있다.
- 주의할 점: 이 기법은 쓰기(Write) 작업의 부하를 증가시킨다.
- 새로운 주문이 들어올 때마다, 주문이 취소될 때마다, 반품이 발생할 때마다 member 테이블의 "total_purchase_amount" 값을 정확하게 갱신하는 로직을 애플리케이션에 반드시 구현해야 한다.
- 이 로직에 실수가 생기면 데이터는 쉽게 깨져버린다.
3. 테이블 통합 및 분할
테이블 간의 관계, 그리고 데이터의 사용 패턴을 분석하여 테이블 구조를 재조정하는 방법이다.
- 테이블 통합: "1:1" 또는 "1:N" 관계에서 항상 함께 조회되는 테이블들을 하나의 테이블로 합쳐 JOIN을 원천적으로 제거한다.
- 예: member와 member_detail 테이블을 하나로 합치는 경우
- 테이블 분할: 하나의 테이블에 컬럼이 너무 많고, 일부 컬럼만 자주 사용될 때 테이블을 수직으로 분할(Vertical Partitioning)하여 디스크 I/O 성능을 높일 수 있다.
- 자주 사용하는 컬럼들과 그렇지 않은 컬럼들을 별도의 테이블로 분리하는 것이다.
역정규화 시 데이터 일관성 유지 방안
역정규화의 가장 큰 대가는 데이터 불일치(Inconsistency) 위험이다. 원본 데이터가 변경되었을 때, 중복된 데이터를 빠짐없이 찾아서 업데이트해주어야 한다. 이 일관성을 유지하는 방법은 다음과 같다.
- 애플리케이션 로직
- 가장 일반적인 방법이다. 개발자가 코드 레벨에서 데이터 일관성을 책임지는 것이다.
- 상품명을 수정하는 로직을 구현할 때, product 테이블을 "UPDATE" 한 후, 관련된 order_item 테이블의 "product_name"도 "UPDATE"하는 코드를 반드시 함께 작성해야 한다.
- 데이터베이스 트리거(Trigger)
- 특정 테이블에 "INSERT", "UPDATE", "DELETE" 같은 이벤트가 발생했을 때, 미리 정의해 둔 다른 SQL을 자동으로 실행시키는 데이터베이스 기능이다.
- 예를 들어, product 테이블에 "UPDATE" 트리거를 설정하여, "product_name" 컬럼이 변경되면 order_item 테이블의 "product_name"을 자동으로 업데이트하게 만들 수 있다.
- 배치(Batch) 작업
- 주기적으로(예: 매일 새벽) 스케줄러를 통해 배치 프로그램을 실행시켜, 원본 데이터와 중복된 데이터 간의 불일치를 찾아내고 동기화시켜주는 방법이다.
- 실시간 일관성이 중요하지 않은 데이터에 사용할 수 있다.
역정규화는 성능 문제를 해결하는 강력한 "양날의 검"이다. 반드시 필요한 곳에, 데이터 일관성을 유지할 수 있는 명확한 계획을 가지고 적용해야 한다.
역정규화, 언제 해야 할까? (실무 가이드)
역정규화는 양날의 검과 같다. 잘못 사용하면 데이터 무결성을 해치고 시스템을 더 복잡하게 만드는 재앙이 될 수 있다. 실무에서는 다음 원칙을 반드시 지켜야 한다.
- 섣불리 적용하지 마라
- 설계 단계에서부터 역정규화를 고려하는 것은 매우 위험하다.
- 먼저 정규화 원칙에 따라 데이터 모델을 설계하는 것이 기본이다.
- 데이터로 증명하라
- "느릴 것 같다"는 추측만으로 역정규화를 진행해서는 안 된다.
- 반드시 서비스 오픈 전 성능 테스트 또는 애플리케이션을 운영하면서 실제 성능 측정을 통해 병목이 되는 쿼리를 명확히 식별해야 한다.
- 읽기/쓰기 비율을 고려하라
- 역정규화는 주로 조회(읽기) 성능을 위해 쓰기 성능과 데이터 정합성을 희생하는 구조다.
- 따라서 쓰기 작업보다 읽기 작업이 압도적으로 많은 경우에 적용하는 것이 효과적이다.
- 비용을 계산하라
- 역정규화를 통해 얻는 성능적 이점과, 그로 인해 발생하는 데이터 불일치 문제 해결 및 유지보수를 위한 개발 비용을 철저히 비교 분석해야 한다.
결론적으로, 역정규화는 정규화된 모델에서 시작하여, 실제 운영 환경에서 발생한 성능 문제를 해결하기 위한 최후의 수단으로 사용되어야 한다. 정규화의 원칙을 깊이 이해하고, 그 위에 시스템의 특성을 고려하여 역정규화를 전략적으로 적용하는 것이 바로 뛰어난 데이터베이스 설계자의 역량이다.
역정규화는 언제 적용하는가?
데이터베이스 설계는 크게 개념적 모델링, 논리적 모델링, 물리적 모델링의 3단계로 진행된다. 우리가 지금까지 배운 정규화는 데이터의 논리적 구조를 정의하는 논리적 모델링 단계의 핵심 과정이. 반면, 역정규화는 논리적 모델이 완성된 후, 실제 데이터베이스의 성능, 저장 공간 등을 고려하여 물리적인 저장 구조를 설계하는 물리적 모델링 단계에서 적용하는 기법이다.
이 순서가 매우 중요하다. 반드시 정규화를 통해 데이터의 정합성과 일관성을 확보한 설계를 먼저 완성해야 한다. 그 이후에, 성능 저하가 예상되는 특정 부분에 한해 역정규화를 신중하게 검토하고 적용해야 한다. 처음부터 역정규화를 염두에 두고 설계하면 데이터 구조의 무결성이 깨지고, 결국 유지보수하기 어려운 복잡한 시스템이 될 위험이 크다. "선 정규화, 후 역정규화" 라는 원칙을 반드시 기억해야 한다.
용어 정리: 역정규화, 반정규화, 비정규화
실무에서는 Denormalization이라는 용어를 역정규화, 반정규화, 비정규화 등으로 번역하여 사용하며, 엄격한 구분 없이 혼용하는 경우가 많다. 그중에서도 주로 "반정규화"와 "역정규화"라는 단어가 많이 사용된다. 각각의 미묘한 차이를 알아보자.
- 반정규화: "정규화에 반대한다"는 직관적인 의미를 가진다. 가장 포괄적인 용어이지만, 기술적인 정밀성은 다소 떨어진다.
- 역정규화: "역"이라는 단어가 의미하듯, 이미 정규화된 모델을 성능 등의 이유로 의도적으로 되돌리는 "과정(Process)"을 표현하는 용어다. 우리는 정규화된 모델에서 출발하여 성능 최적화를 위해 의도적으로 중복을 추가하는 작업을 할 것이므로, 이 강의에서는 "역정규화"라는 용어를 사용한다.
- 비정규화: 정규화가 전혀 적용되지 않은 상태나 구조를 의미한다. 설계 초기 단계에서 정규화를 거치지 않았거나, NoSQL처럼 처음부터 정규화 원칙을 따르지 않는 데이터 모델을 설명할 때 적합한 용어다.
'데이터 베이스 > 설계 1편, 현대적 데이터 모델링 완전 정복' 카테고리의 다른 글
| Ch11. 물리적 모델링 실습 - 설계 (0) | 2026.07.05 |
|---|---|
| Ch10. 물리적 모델링 - 테이블 정의서 (0) | 2026.07.01 |
| Ch10. 물리적 모델링 - 날짜와 시간 타입 (0) | 2026.07.01 |
| Ch10. 물리적 모델링 - 문자, 숫자, PK 타입 (0) | 2026.07.01 |
| Ch09. 정규화 - 시작 (0) | 2026.06.29 |