정규화의 본질과 단계
정규화는 데이터 중복을 제거하고 이상(anomaly)을 방지하기 위한 데이터 모델링 기법입니다. 실무에서는 보통 3NF(제3정규형)까지 적용하며, 성능상 필요한 경우 의도적으로 비정규화합니다.
정규화 단계 요약
| 단계 | 조건 | 제거 대상 | 예시 |
|---|---|---|---|
| 1NF | 원자값만 허용 | 반복 그룹 | 전화번호 복수값 분리 |
| 2NF | 완전 함수적 종속 | 부분 종속 | 복합키 일부에만 종속된 컬럼 |
| 3NF | 이행적 종속 제거 | 간접 종속 | 우편번호 → 도시 분리 |
| BCNF | 결정자가 모두 후보키 | 비후보키 결정자 | 교수-과목 관계 |
정규화 적용 예시
-- 비정규 상태 (주문 테이블)
CREATE TABLE orders_denorm (
order_id INT PRIMARY KEY,
customer_name VARCHAR(100),
customer_email VARCHAR(100), -- 고객 변경 시 모든 주문 수정 필요
product_name VARCHAR(100),
product_price DECIMAL(10,2), -- 가격 변경 시 불일치 발생
quantity INT,
order_date TIMESTAMP
);
-- 3NF 적용
CREATE TABLE customers (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL
);
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
price DECIMAL(10,2) NOT NULL
);
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
customer_id INT REFERENCES customers(id),
order_date TIMESTAMP DEFAULT NOW()
);
CREATE TABLE order_items (
id SERIAL PRIMARY KEY,
order_id INT REFERENCES orders(id),
product_id INT REFERENCES products(id),
quantity INT NOT NULL,
unit_price DECIMAL(10,2) NOT NULL -- 주문 시점 가격 스냅샷
);
비정규화가 필요한 상황
-- 읽기 성능 최적화: 게시글 목록 조회
-- 정규화 상태 → JOIN 필요
SELECT p.*, u.name AS author_name, COUNT(c.id) AS comment_count
FROM posts p
JOIN users u ON p.author_id = u.id
LEFT JOIN comments c ON c.post_id = p.id
GROUP BY p.id, u.name;
-- 비정규화 → JOIN 없이 바로 조회
ALTER TABLE posts ADD COLUMN author_name VARCHAR(100);
ALTER TABLE posts ADD COLUMN comment_count INT DEFAULT 0;
SELECT id, title, author_name, comment_count
FROM posts
ORDER BY created_at DESC
LIMIT 20;
비정규화 판단 기준
- 읽기 비율: 읽기가 쓰기보다 10배 이상 많으면 비정규화 고려
- 조인 비용: 3개 이상의 테이블 조인이 자주 발생하는 쿼리
- 데이터 변경 빈도: 원본 데이터가 거의 변하지 않는 경우
- 정합성 허용 범위: 약간의 지연된 일관성을 허용할 수 있는 경우
- 집계 쿼리: COUNT, SUM 등을 미리 계산하여 저장
비정규화 동기화 전략
-- 트리거를 이용한 자동 동기화
CREATE OR REPLACE FUNCTION update_comment_count()
RETURNS TRIGGER AS $$
BEGIN
IF TG_OP = 'INSERT' THEN
UPDATE posts SET comment_count = comment_count + 1
WHERE id = NEW.post_id;
ELSIF TG_OP = 'DELETE' THEN
UPDATE posts SET comment_count = comment_count - 1
WHERE id = OLD.post_id;
END IF;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_comment_count
AFTER INSERT OR DELETE ON comments
FOR EACH ROW EXECUTE FUNCTION update_comment_count();
정규화와 비정규화는 이분법이 아닌 스펙트럼입니다. 데이터 모델의 정규화로 시작하되, 성능 병목이 측정된 지점에서만 의도적으로 비정규화하는 것이 가장 실용적인 접근입니다.
댓글 0