반정규화로 설계하는 데이터베이스 조회 성능

반정규화의 유형과 데이터 정합성 관리 방법을 정리하고, 조인·디스크 I/O 병목을 줄이기 위한 데이터베이스 설계 판단 기준을 다룬다.

2026-08-15 · 최초 발행 2025-08-10

조회 병목이 설계를 다시 보게 만드는 시점

정규화는 중복을 줄이고 데이터 무결성을 지키는 데 적합하다. 다만 테이블 관계가 복잡해지면 조인이 늘고, 여러 테이블을 읽는 과정에서 쿼리 성능과 디스크 I/O가 부담이 될 수 있다. 실시간 응답이 필요한 시스템에서는 이 비용이 설계 선택의 기준이 된다.

반정규화(De-Normalization)는 성능 개선과 시스템 단순화를 위해 정규화된 모델에 의도적인 중복을 도입하는 과정이다. 정규화의 대체재라기보다, 정규화를 기반으로 조회 경로를 조정하는 최적화 전략에 가깝다.

이 선택은 항상 다음의 균형을 전제로 한다.

  • 데이터 정합성과 무결성, 성능 향상
  • 저장 공간 효율성, 조회 속도 개선
  • 갱신 비용 증가, 조회 비용 감소

테이블 구조에서 조인과 데이터량을 조정하는 방법

관련 테이블을 합쳐 조인을 없애거나, 반대로 큰 테이블을 접근 방식에 맞춰 나눌 수 있다.

테이블 병합

함께 조회되는 테이블을 통합하면 조인 연산을 줄일 수 있다. 고객과 주문 정보를 하나의 테이블로 다루는 경우가 그 예다.

⤢✕정규화: 고객 테이블1:N 관계정규화: 주문 테이블반정규화: 통합 고객주문테이블

테이블 분할

대용량 테이블은 관리와 조회 특성에 따라 분할할 수 있다. 수직 분할은 자주 쓰는 컬럼과 드물게 쓰는 컬럼을 분리하는 방식이다.

⤢✕원본 테이블자주 사용하는 컬럼 테이블드물게 사용하는 컬럼 테이블

수평 분할은 연도별·지역별처럼 특정 기준에 따라 행을 나누는 방식이다.

⤢✕전체 주문 테이블2022년 주문2023년 주문2024년 주문

조회 목적의 별도 테이블

집계 결과, 변경 이력, 자주 접근하는 데이터는 별도 테이블로 관리할 수 있다. 통계 테이블에는 미리 계산한 집계 데이터를 저장하고, 이력 테이블에는 변경 내역을 남기며, 부분 테이블에는 빈번히 접근하는 데이터만 둔다.

일별 판매 통계 테이블의 예시는 다음과 같다.

CREATE TABLE DailySalesStatistics (
    date DATE PRIMARY KEY,
    total_sales DECIMAL(10,2),
    order_count INT,
    average_order_value DECIMAL(8,2)
);

컬럼에 중복과 계산 결과를 담는 경우

다른 테이블의 값을 함께 저장하면 조인을 줄일 수 있다. 주문 테이블에 고객명을 중복으로 보관하는 방식이 대표적이다.

⤢✕정규화: 주문 테이블order_idcustomer_idorder_date반정규화정규화: 고객 테이블customer_idcustomer_nameemail반정규화: 주문 테이블order_idcustomer_idcustomer_nameorder_date

계산 비용이 큰 값은 파생 컬럼으로 저장할 수 있다. 주문의 총 금액을 별도 컬럼에 두는 예시는 다음과 같다.

ALTER TABLE Orders ADD total_amount DECIMAL(10,2);

변경 이력을 추적하려면 메타데이터 컬럼을 추가할 수 있다.

ALTER TABLE Products
ADD last_update_date DATETIME,
ADD last_updated_by VARCHAR(50);

조인 없이 조회할 수 있도록 PK 컬럼을 추가하는 방식도 컬럼 반정규화에 포함된다.

관계를 하나 더 만들어 조회 경로를 확보한다

테이블 간 접근 경로가 여러 개 필요하다면 중복 관계를 둘 수 있다. 고객에서 주문을 거쳐 상품으로 접근하던 구조에 고객과 상품 간 관계를 추가하는 방식이다.

⤢✕중복 관계고객주문상품

상품 조회와 거래 이력에서의 적용

상품 상세 페이지가 상품, 카테고리, 판매자, 리뷰 정보를 조회하면서 다중 조인을 수행하면 로딩 성능이 떨어질 수 있다. 이 경우 상품 테이블에 카테고리명과 판매자명을 중복 저장하고, 평균 평점과 리뷰 수를 파생 컬럼으로 추가하며, 인기 상품을 위한 캐시 테이블을 별도로 둘 수 있다. 이 전략의 결과로 페이지 로딩 시간은 75% 감소하고 서버 부하는 60% 감소했다.

전자상거래 시스템에서 주문 내역을 조회할 때 주문상세와 상품 정보를 함께 표시하면 주문, 주문상세, 상품 테이블을 거치는 3중 조인이 성능 저하 요인이 될 수 있다. 이 경우 주문상세 테이블에 상품명, 가격처럼 자주 조회하는 상품 정보를 중복 저장할 수 있다. 사례에서는 조회 성능이 60% 향상됐고, 상품 정보가 바뀔 때 동기화 로직이 추가됐다.

금융 시스템에서 대용량 거래 이력을 조회하고 분석할 때도 반정규화를 적용할 수 있다. 거래 테이블을 연도별로 수평 분할하고, 빈번히 조회하는 계정 정보를 거래 테이블에 중복 저장하며, 일/월/연 단위 집계 통계 테이블을 생성하는 방식이다. 이력 조회 쿼리 성능은 90% 향상됐고 분석 작업 처리 시간은 80% 단축됐다.

대량 데이터를 기반으로 통계를 분석하는 데이터 웨어하우스에서는 복잡한 집계 연산이 응답 지연을 만들 수 있다. 사전 집계 테이블을 생성해 통계 값을 저장하면 분석 쿼리 응답 시간을 90% 단축할 수 있지만, 데이터 갱신 주기를 관리해야 한다.

적용 전 확인할 운영 조건

UPDATE와 INSERT가 빈번한 테이블은 반정규화 이후 오버헤드가 커질 수 있다. 반대로 SELECT 비중이 높은 테이블은 반정규화의 대상이 될 수 있다.

문서 지향 데이터베이스인 MongoDB 등은 중첩 문서 구조를 통해 데이터 접근을 최적화하는 반정규화 지향 특성을 가진다. 마이크로서비스 아키텍처에서도 서비스별 독립 데이터베이스 운영으로 데이터 중복이 발생하며, 이벤트 소싱과 CQRS 패턴은 이 환경에서 데이터 일관성을 관리하는 방법이 될 수 있다. 실시간 분석 환경에서는 스트리밍 데이터를 효율적으로 처리할 수 있는 데이터 구조가 필요하고, 반정규화는 실시간 처리 요구에 맞춰 데이터 접근을 조정하는 수단이 된다.

반정규화의 판단 기준은 데이터 특성과 사용 패턴이다. 성능 개선만을 위해 넓게 적용하면 일관성과 유지보수성이 흔들릴 수 있으므로, 비즈니스 요구사항과 시스템 특성에 맞는 균형을 찾아야 한다. 현재 데이터 규모뿐 아니라 앞으로의 데이터 볼륨과 성장률도 설계에 반영해야 하며, 대용량 데이터에서는 반정규화 효과가 크게 나타날 수 있다.

중복 데이터를 도입했다면 이를 일관되게 갱신할 장치가 필요하다. 트리거, 저장 프로시저, 배치 프로세스가 그 역할을 맡을 수 있다. 또한 적용 전후 성능을 객관적으로 측정하고, 여러 워크로드 상황에서 결과를 검증해야 한다.

중복 데이터를 갱신하는 구현 예시

카테고리명이 바뀔 때 상품 테이블의 중복 컬럼을 갱신하는 트리거는 다음과 같다.

CREATE TRIGGER update_product_category_name
AFTER UPDATE ON Categories
FOR EACH ROW
BEGIN
    UPDATE Products
    SET category_name = NEW.category_name
    WHERE category_id = NEW.category_id;
END;

일별 판매 통계를 갱신하는 저장 프로시저의 예시는 다음과 같다.

CREATE PROCEDURE update_daily_sales_statistics()
BEGIN
    INSERT INTO DailySalesStatistics
    SELECT
        DATE(order_date) as date,
        SUM(total_amount) as total_sales,
        COUNT(*) as order_count,
        AVG(total_amount) as average_order_value
    FROM Orders
    WHERE DATE(order_date) = DATE(NOW() - INTERVAL 1 DAY)
    GROUP BY DATE(order_date)
    ON DUPLICATE KEY UPDATE
        total_sales = VALUES(total_sales),
        order_count = VALUES(order_count),
        average_order_value = VALUES(average_order_value);
END;

연도별로 나눈 주문 테이블을 하나의 조회 대상으로 묶는 뷰는 다음과 같이 만들 수 있다.

CREATE VIEW all_orders AS
SELECT * FROM orders_2022
UNION ALL
SELECT * FROM orders_2023
UNION ALL
SELECT * FROM orders_2024;

병목에서 검증까지 이어지는 판단 과정

반정규화는 실제 성능 문제가 발생한 지점에서 시작해야 한다. 실행 계획 분석과 성능 프로파일링으로 병목을 찾고, 읽기와 쓰기 비율 및 반복되는 쿼리 패턴을 확인한다. 그 다음 개발·유지보수 비용과 성능 개선 효과, 데이터 정합성 위험과 사용자 경험 향상을 함께 비교한다.

효과가 큰 부분부터 점진적으로 적용하고, 각 단계에서 성능을 측정해 검증하는 흐름이 필요하다.

⤢✕읽기 위주쓰기 위주예아니오성능 문제 식별데이터 접근 패턴 분석읽기/쓰기 비율?반정규화 고려정규화 유지후보 기법 식별비용-효익 분석테스트 환경 구현성능 측정성능 개선?실제 환경 적용

반정규화의 가치는 중복 자체에 있지 않다. 시스템 요구사항, 데이터 특성, 사용 패턴을 기준으로 정합성 관리 비용보다 조회 성능 개선의 이점이 큰 지점을 선택하는 데 있다.

반정규화데이터베이스 설계쿼리 최적화데이터 정합성성능 최적화