정규화는 왜 필요할까
테이블을 만들다 보면 처음에는 한 테이블에 모든 정보를 넣고 싶어집니다. 주문 번호, 회원 이름, 상품명, 가격, 수량, 배송지까지 한 줄에 넣으면 조회하기 쉬워 보입니다.
하지만 데이터가 늘어나면 문제가 생깁니다. 같은 회원 이름이 여러 줄에 반복되고, 상품 가격이 여러 곳에 복사되며, 한 곳만 수정하고 다른 곳은 수정하지 못하는 상황이 생깁니다. 이런 구조는 오래 유지하기 어렵습니다.
정규화는 중복을 줄이고 변경에 강한 테이블 구조를 만드는 과정입니다. 단순히 테이블을 많이 나누는 기술이 아닙니다. 핵심은 다음 질문에 답하는 것입니다.
이 데이터는 어디에 한 번만 저장되어야 하는가?
데이터 모델링은 이 질문에 답하며 테이블, 열, 관계를 설계하는 일입니다. 정규화를 이해하면 테이블이 왜 나뉘어야 하는지 스스로 판단할 수 있습니다.

나쁜 주문 테이블에서 시작하기
다음과 같은 주문 테이블을 생각해 봅시다.
CREATE TABLE order_history (
order_id INT,
order_date DATE,
customer_id INT,
customer_name VARCHAR(50),
customer_email VARCHAR(100),
product_ids VARCHAR(100),
product_names VARCHAR(255),
product_prices VARCHAR(255),
quantities VARCHAR(100),
total_amount INT
);데이터는 다음처럼 들어갈 수 있습니다.
| order_id | customer_name | product_names | product_prices | quantities |
|---|---|---|---|---|
| 1 | 김민수 | 키보드,마우스 | 30000,15000 | 1,2 |
| 2 | 김민수 | 모니터 | 200000 | 1 |
겉으로는 한 주문을 한 줄로 볼 수 있어 편합니다. 하지만 SQL로 다루기 어렵습니다.
예를 들어 마우스를 구매한 주문을 찾으려면 product_names 안의 문자열을 검색해야 합니다. 상품별 판매 수량을 집계하려면 쉼표로 이어진 값을 다시 분리해야 합니다. MySQL이 잘하는 테이블 연산을 제대로 활용하지 못합니다.
이제 이 구조를 정규화해 보겠습니다.
제1정규형: 한 칸에는 하나의 값만 넣는다
제1정규형은 각 열이 원자값을 가져야 한다는 규칙입니다. 원자값이란 더 이상 나누지 않아도 되는 하나의 값입니다.
다음 값은 제1정규형에 맞지 않습니다.
product_names = '키보드,마우스'
product_prices = '30000,15000'
quantities = '1,2'한 칸에 여러 상품이 들어 있습니다. 이런 구조에서는 개별 상품을 조건으로 조회하거나 집계하기 어렵습니다.
주문과 주문 상품을 나누면 제1정규형에 가까워집니다.
CREATE TABLE orders (
id INT PRIMARY KEY,
customer_id INT NOT NULL,
order_date DATE NOT NULL,
total_amount INT NOT NULL
);
CREATE TABLE order_items (
order_id INT NOT NULL,
product_id INT NOT NULL,
product_name VARCHAR(100) NOT NULL,
unit_price INT NOT NULL,
quantity INT NOT NULL,
PRIMARY KEY (order_id, product_id)
);이제 한 주문에 상품이 여러 개라면 order_items에 여러 행을 넣습니다.
| order_id | product_id | product_name | unit_price | quantity |
|---|---|---|---|---|
| 1 | 101 | 키보드 | 30000 | 1 |
| 1 | 102 | 마우스 | 15000 | 2 |
| 2 | 103 | 모니터 | 200000 | 1 |
이 구조에서는 상품별 판매 수량을 쉽게 구할 수 있습니다.
SELECT
product_id,
product_name,
SUM(quantity) AS total_quantity
FROM order_items
GROUP BY product_id, product_name;제1정규형의 핵심은 단순합니다.
한 열에 여러 값을 넣지 않습니다.
반복되는 열을 만들지 않습니다.
여러 개가 생길 수 있는 데이터는 별도 행으로 저장합니다.
제2정규형: 기본키 전체에 의존하게 만든다
제2정규형은 주로 복합 기본키가 있을 때 중요합니다. 복합 기본키란 두 개 이상의 열을 합쳐 기본키로 사용하는 방식입니다.
앞의 order_items는 다음 두 열을 합쳐 기본키로 사용했습니다.
PRIMARY KEY (order_id, product_id)이 말은 한 주문 안에서 같은 상품이 한 번만 나타난다는 뜻입니다.
그런데 order_items에 다음 열이 함께 있다고 해 봅시다.
order_id, product_id, product_name, unit_price, quantity, customer_name여기서 quantity는 order_id와 product_id가 함께 정해져야 알 수 있습니다. 어떤 주문에서 어떤 상품을 몇 개 샀는지를 뜻하기 때문입니다.
하지만 product_name은 product_id만 알면 결정됩니다. customer_name은 order_id를 통해 주문을 찾고, 그 주문의 회원을 통해 결정됩니다.
즉, 일부 열이 복합 기본키 전체가 아니라 일부에만 의존합니다. 이것을 부분 종속이라고 합니다.
제2정규형은 이런 부분 종속을 제거합니다. 상품 정보는 상품 테이블로 분리합니다.
CREATE TABLE products (
id INT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
current_price INT NOT NULL
);
CREATE TABLE order_items (
order_id INT NOT NULL,
product_id INT NOT NULL,
quantity INT NOT NULL,
PRIMARY KEY (order_id, product_id)
);이제 product_name은 products에 한 번만 저장됩니다. order_items는 주문과 상품의 연결, 그리고 수량만 저장합니다.
제2정규형의 핵심은 다음과 같습니다.
복합 기본키를 사용할 때 특히 확인합니다.
기본키의 일부만으로 결정되는 열은 분리합니다.
관계 자체의 정보만 연결 테이블에 남깁니다.
제3정규형: 기본키가 아닌 열끼리 의존하지 않게 만든다
제3정규형은 기본키가 아닌 열이 다른 일반 열에 의존하지 않도록 만드는 규칙입니다.
회원 테이블을 예로 들어 보겠습니다.
CREATE TABLE customers (
id INT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
email VARCHAR(100) NOT NULL,
grade_name VARCHAR(20) NOT NULL,
grade_discount_rate DECIMAL(5,2) NOT NULL
);여기서 grade_discount_rate는 회원 자체보다 grade_name에 의해 결정됩니다.
customer_id -> grade_name -> grade_discount_rate즉, 회원의 기본키인 id가 할인율을 직접 결정한다기보다, 회원 등급이 할인율을 결정합니다. 이런 구조를 이행 종속이라고 합니다.
등급 정책이 바뀌면 같은 등급을 가진 모든 회원 행을 수정해야 합니다. 한 행이라도 빠지면 데이터가 서로 달라집니다.
등급을 별도 테이블로 분리하면 더 안정적입니다.
CREATE TABLE customer_grades (
id INT PRIMARY KEY,
name VARCHAR(20) NOT NULL,
discount_rate DECIMAL(5,2) NOT NULL
);
CREATE TABLE customers (
id INT PRIMARY KEY,
grade_id INT NOT NULL,
name VARCHAR(50) NOT NULL,
email VARCHAR(100) NOT NULL,
FOREIGN KEY (grade_id) REFERENCES customer_grades(id)
);제3정규형의 핵심은 다음과 같습니다.
일반 열이 다른 일반 열을 설명하고 있지 않은지 확인합니다.
정책, 분류, 상태처럼 따로 관리될 수 있는 값은 별도 테이블 후보입니다.
값이 바뀔 때 여러 행을 동시에 수정해야 한다면 분리를 의심합니다.
중복 저장이 만드는 세 가지 이상 현상
정규화를 배우는 이유는 이론을 외우기 위해서가 아닙니다. 중복 저장이 실제로 문제를 만들기 때문입니다. 대표적인 문제가 삽입 이상, 수정 이상, 삭제 이상입니다.
삽입 이상
상품 정보를 주문 테이블에만 저장한다고 가정해 봅시다.
order_id, customer_name, product_name, product_price, quantity아직 한 번도 팔리지 않은 새 상품을 등록하고 싶습니다. 그런데 상품을 저장할 별도 테이블이 없습니다. 주문이 있어야만 상품 정보를 넣을 수 있습니다.
이처럼 어떤 데이터를 넣고 싶은데 불필요한 다른 데이터가 함께 필요해지는 문제가 삽입 이상입니다.
상품은 주문 여부와 관계없이 존재할 수 있습니다. 따라서 products 테이블이 따로 있어야 합니다.
수정 이상
product_name = '무선 마우스'의 가격이 15000원에서 17000원으로 바뀌었다고 해 봅시다.
상품 가격이 주문 내역 여러 행에 중복 저장되어 있다면 모든 행을 수정해야 합니다. 일부 행만 수정되면 같은 상품인데 가격이 서로 다르게 보입니다.
이것이 수정 이상입니다.
다만 주문에서의 가격은 주의해야 합니다. 현재 상품 가격과 주문 당시 판매 가격은 다릅니다. 주문 당시 가격은 거래 기록이므로 보존해야 합니다. 따라서 products.current_price와 order_items.unit_price가 동시에 존재할 수 있습니다.
이 경우 order_items.unit_price는 단순 중복이 아니라 구매 시점의 가격 스냅샷입니다.
삭제 이상
어떤 상품의 마지막 주문 내역을 삭제했더니 그 상품의 이름과 가격 정보까지 사라졌다고 해 봅시다. 주문 기록을 지웠을 뿐인데 상품 정보가 같이 사라졌습니다.
이것이 삭제 이상입니다.
상품 정보와 주문 정보가 한 테이블에 섞여 있었기 때문에 생긴 문제입니다. 서로 다른 생명주기를 가진 데이터는 분리해야 합니다.
정규화할 때 기준이 되는 질문
테이블을 나눌지 말지 고민될 때는 다음 질문을 던지면 좋습니다.
이 데이터는 독립적으로 생성될 수 있는가?
이 데이터는 독립적으로 수정될 수 있는가?
이 데이터는 독립적으로 삭제될 수 있는가?
같은 값이 여러 행에 반복되는가?
반복되는 값이 바뀌면 여러 곳을 수정해야 하는가?
이 데이터 사이에 1:N 또는 N:M 관계가 있는가?
예를 들어 회원과 게시글은 분리해야 합니다. 회원 한 명이 게시글을 여러 개 쓸 수 있기 때문입니다.
members 1 : N posts게시글과 댓글도 분리해야 합니다.
posts 1 : N comments주문과 상품은 N:M 관계입니다. 한 주문에는 여러 상품이 들어갈 수 있고, 한 상품은 여러 주문에 포함될 수 있습니다. N:M 관계는 중간 테이블로 풉니다.
orders 1 : N order_items N : 1 productsorder_items는 주문과 상품 사이의 연결 테이블입니다. 동시에 수량, 주문 당시 가격 같은 관계의 속성을 저장합니다.
비정규화는 언제 선택할까
정규화가 항상 정답은 아닙니다. 실무에서는 성능과 편의성을 위해 일부 데이터를 의도적으로 중복 저장하기도 합니다. 이를 비정규화라고 합니다.
비정규화는 정규화를 모를 때 하는 것이 아닙니다. 정규화된 구조를 이해한 뒤, 필요에 따라 일부 중복을 허용하는 선택입니다.
비정규화를 고려할 수 있는 경우
다음 조건이 함께 맞을 때 비정규화를 고려합니다.
같은 조인을 매우 자주 수행한다.
읽기 요청이 쓰기 요청보다 훨씬 많다.
집계 결과를 매번 계산하기에 비용이 크다.
중복된 값을 갱신하는 절차를 명확히 통제할 수 있다.
데이터가 약간 늦게 반영되어도 업무상 문제가 없다.
예를 들어 게시글 목록에서 댓글 수를 항상 보여 준다고 합시다.
정규화된 방식으로는 댓글 테이블을 매번 집계합니다.
SELECT
p.id,
p.title,
COUNT(c.id) AS comment_count
FROM posts p
LEFT JOIN comments c ON c.post_id = p.id
GROUP BY p.id, p.title;게시글과 댓글이 많아지면 이 집계가 부담이 될 수 있습니다. 이때 posts 테이블에 comment_count 열을 둘 수 있습니다.
ALTER TABLE posts ADD COLUMN comment_count INT NOT NULL DEFAULT 0;댓글이 추가되면 comment_count를 1 증가시키고, 댓글이 삭제되면 1 감소시킵니다.
UPDATE posts
SET comment_count = comment_count + 1
WHERE id = 10;이 구조는 중복입니다. 실제 댓글 수는 comments를 세면 알 수 있기 때문입니다. 하지만 목록 조회 성능을 위해 의도적으로 저장할 수 있습니다.
비정규화할 때 조심할 점
비정규화는 편하지만 위험합니다. 중복된 값이 서로 달라질 수 있기 때문입니다.
따라서 다음 원칙을 지켜야 합니다.
먼저 정규화된 구조로 설계합니다.
느린 지점을 확인한 뒤에 비정규화합니다.
중복 값을 갱신하는 책임을 한 곳에 모읍니다.
주기적으로 원본 데이터와 맞는지 검증합니다.
돈, 재고, 권한처럼 정확성이 중요한 데이터는 특히 신중하게 다룹니다.
비정규화는 성능 개선 도구입니다. 설계의 출발점이 아니라, 측정 후 선택하는 조정 수단에 가깝습니다.
작은 서비스 ERD 설계하기
이제 회원, 게시글, 댓글, 주문 기능을 가진 작은 서비스를 설계해 보겠습니다.
서비스 요구사항은 다음과 같습니다.
회원은 가입하고 로그인할 수 있습니다.
회원은 게시글을 작성할 수 있습니다.
회원은 게시글에 댓글을 작성할 수 있습니다.
회원은 상품을 주문할 수 있습니다.
한 주문에는 여러 상품이 포함될 수 있습니다.
상품 가격은 바뀔 수 있지만, 주문 당시 가격은 보존해야 합니다.
이 요구사항에서 주요 엔터티를 뽑으면 다음과 같습니다.
회원: members
게시글: posts
댓글: comments
상품: products
주문: orders
주문 상품: order_items관계는 다음과 같습니다.
members 1 : N posts
members 1 : N comments
posts 1 : N comments
members 1 : N orders
orders 1 : N order_items
products 1 : N order_items텍스트로 ERD를 표현하면 다음과 같습니다.
members
├─< posts
│ └─< comments
├─< comments
└─< orders
└─< order_items >─ products여기서 < 표시는 여러 개를 뜻합니다. 회원 한 명은 게시글을 여러 개 쓸 수 있고, 주문 한 건은 주문 상품을 여러 개 가질 수 있습니다.
테이블 설계 예시
아래 예시는 학습용으로 단순화한 구조입니다. 실제 서비스에서는 로그인 보안, 결제, 배송, 환불, 재고, 주문 상태 이력 등을 더 세밀하게 설계해야 합니다.
회원 테이블
CREATE TABLE members (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
email VARCHAR(255) NOT NULL UNIQUE,
name VARCHAR(50) NOT NULL,
password_hash VARCHAR(255) NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);회원 이메일은 중복되면 안 되므로 UNIQUE 제약을 둡니다. 비밀번호는 원문으로 저장하지 않고 해시된 값을 저장해야 합니다.
게시글 테이블
CREATE TABLE posts (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
member_id BIGINT NOT NULL,
title VARCHAR(200) NOT NULL,
content TEXT NOT NULL,
view_count INT NOT NULL DEFAULT 0,
comment_count INT NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NULL,
FOREIGN KEY (member_id) REFERENCES members(id)
);posts.member_id는 글 작성자를 가리킵니다. comment_count는 비정규화 예시입니다. 댓글 목록을 매번 세지 않고 빠르게 보여 주기 위해 둘 수 있습니다.
댓글 테이블
CREATE TABLE comments (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
post_id BIGINT NOT NULL,
member_id BIGINT NOT NULL,
parent_comment_id BIGINT NULL,
content TEXT NOT NULL,
is_deleted TINYINT(1) NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (post_id) REFERENCES posts(id),
FOREIGN KEY (member_id) REFERENCES members(id),
FOREIGN KEY (parent_comment_id) REFERENCES comments(id)
);댓글은 게시글에 속하고, 작성자인 회원과도 연결됩니다. parent_comment_id는 대댓글을 표현할 때 사용할 수 있습니다. 필요하지 않다면 빼도 됩니다.
is_deleted는 댓글을 실제로 삭제하지 않고 삭제 표시만 할 때 사용합니다. 댓글을 물리적으로 삭제하면 대댓글 구조가 깨질 수 있기 때문입니다.
상품 테이블
CREATE TABLE products (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
current_price INT NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'ON_SALE',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);current_price는 현재 판매 가격입니다. 가격은 시간이 지나며 바뀔 수 있습니다.
주문 테이블
CREATE TABLE orders (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
member_id BIGINT NOT NULL,
order_status VARCHAR(20) NOT NULL DEFAULT 'CREATED',
total_amount INT NOT NULL DEFAULT 0,
ordered_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (member_id) REFERENCES members(id)
);주문은 회원에게 속합니다. total_amount는 주문 상품 금액의 합계입니다. 엄밀히 말하면 order_items를 계산해서 구할 수 있으므로 중복입니다. 하지만 주문 총액은 자주 조회되고, 주문 당시 금액을 보존해야 하므로 주문 테이블에 저장하는 경우가 많습니다.
주문 상품 테이블
CREATE TABLE order_items (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
order_id BIGINT NOT NULL,
product_id BIGINT NOT NULL,
product_name VARCHAR(100) NOT NULL,
unit_price INT NOT NULL,
quantity INT NOT NULL,
FOREIGN KEY (order_id) REFERENCES orders(id),
FOREIGN KEY (product_id) REFERENCES products(id)
);product_name과 unit_price는 products에 있는 값과 겹쳐 보입니다. 하지만 여기서는 주문 당시의 상품명과 가격을 보존하기 위한 스냅샷입니다.
상품명이 나중에 바뀌어도 과거 주문서의 상품명이 함께 바뀌면 안 됩니다. 주문 기록은 당시 거래 사실을 나타내야 하기 때문입니다.
설계를 검증하는 조회 예시
테이블을 설계한 뒤에는 실제로 필요한 조회가 가능한지 확인해야 합니다.
회원이 작성한 게시글 목록은 다음처럼 조회합니다.
SELECT
p.id,
p.title,
p.created_at
FROM posts p
WHERE p.member_id = 1
ORDER BY p.created_at DESC;게시글과 작성자 이름을 함께 보고 싶다면 조인합니다.
SELECT
p.id,
p.title,
m.name AS writer_name,
p.created_at
FROM posts p
JOIN members m ON m.id = p.member_id
ORDER BY p.created_at DESC;특정 주문의 상세 내역은 orders, order_items, products를 연결해 조회할 수 있습니다.
SELECT
o.id AS order_id,
o.ordered_at,
oi.product_name,
oi.unit_price,
oi.quantity,
oi.unit_price * oi.quantity AS line_amount
FROM orders o
JOIN order_items oi ON oi.order_id = o.id
WHERE o.id = 100;여기서는 products를 조인하지 않았습니다. 주문 당시 상품명과 가격이 order_items에 저장되어 있기 때문입니다. 현재 상품 상태가 필요할 때만 products를 추가로 조인하면 됩니다.
SELECT
oi.product_name AS ordered_product_name,
oi.unit_price AS ordered_price,
p.name AS current_product_name,
p.current_price
FROM order_items oi
JOIN products p ON p.id = oi.product_id
WHERE oi.order_id = 100;이처럼 모델링은 조회와 함께 검증해야 합니다. 테이블만 그럴듯하게 나누는 것으로 끝나지 않습니다.
실무형 모델링 순서
실무에서 데이터 모델을 만들 때는 다음 순서로 접근하면 좋습니다.
1. 업무 규칙을 문장으로 쓴다
먼저 테이블을 만들지 말고 업무 규칙을 문장으로 정리합니다.
회원은 여러 게시글을 작성할 수 있다.
게시글은 여러 댓글을 가질 수 있다.
회원은 여러 주문을 할 수 있다.
주문은 여러 상품을 포함할 수 있다.
상품은 여러 주문에 포함될 수 있다.문장이 명확해야 관계도 명확해집니다.
2. 명사를 엔터티 후보로 뽑는다
문장에서 중요한 명사를 찾습니다.
회원, 게시글, 댓글, 주문, 상품이 명사들은 테이블 후보가 됩니다.
3. 동사를 관계로 바꾼다
문장의 동사는 관계를 알려 줍니다.
회원이 게시글을 작성한다.
게시글이 댓글을 가진다.
회원이 주문한다.
주문이 상품을 포함한다.이 관계를 1:1, 1:N, N:M 중 하나로 판단합니다.
4. N:M 관계는 중간 테이블로 푼다
주문과 상품은 N:M 관계입니다. 이 관계는 직접 연결하지 않고 order_items로 풉니다.
중간 테이블에는 단순히 외래키만 들어가는 것이 아닙니다. 관계에서 발생하는 속성도 들어갑니다.
주문 상품 수량
주문 당시 가격
주문 당시 상품명5. 변경 이유가 같은 데이터끼리 묶는다
테이블을 나누는 좋은 기준은 변경 이유입니다.
회원 이름이 바뀌는 이유와 게시글 제목이 바뀌는 이유는 다릅니다. 따라서 회원과 게시글은 다른 테이블이어야 합니다.
상품의 현재 가격이 바뀌는 이유와 주문 당시 가격이 보존되어야 하는 이유도 다릅니다. 따라서 products.current_price와 order_items.unit_price는 서로 다른 의미를 가집니다.
6. 제약 조건을 함께 설계한다
데이터 모델링은 테이블 이름만 정하는 일이 아닙니다. 제약 조건도 함께 정해야 합니다.
반드시 있어야 하는 값은
NOT NULL로 둡니다.중복되면 안 되는 값은
UNIQUE로 둡니다.다른 테이블을 참조하는 값은 외래키로 연결합니다.
상태 값은 가능한 범위를 애플리케이션이나 별도 코드 테이블로 관리합니다.
제약 조건은 데이터베이스가 지켜 주는 최소한의 안전장치입니다.
정규화와 실무 감각의 균형
정규화는 중복을 줄입니다. 중복이 줄면 수정 실수가 줄고, 데이터의 의미가 명확해집니다. 하지만 모든 것을 끝까지 나누면 조회가 복잡해지고 조인이 많아질 수 있습니다.
그래서 실무에서는 다음 균형이 필요합니다.
기본 구조는 정규화한다.
거래 기록처럼 보존이 중요한 값은 스냅샷으로 저장한다.
자주 쓰는 집계 값은 필요할 때 비정규화한다.
비정규화한 값은 갱신 규칙을 반드시 정한다.
성능 문제는 추측이 아니라 측정 후 해결한다.
좋은 모델은 단순히 정규형 이름을 만족하는 구조가 아닙니다. 업무 규칙을 정확히 담고, 변경에 강하며, 필요한 조회를 안정적으로 처리하는 구조입니다.
마무리
정규화는 데이터 중복을 줄이고 이상 현상을 막기 위한 설계 방법입니다. 제1정규형은 한 칸에 하나의 값만 저장하게 하고, 제2정규형은 복합 기본키의 일부에만 의존하는 열을 제거하며, 제3정규형은 일반 열끼리의 의존을 줄입니다.
하지만 실무에서는 정규화만으로 끝나지 않습니다. 주문 당시 가격처럼 보존해야 하는 값은 별도로 저장해야 하고, 댓글 수처럼 자주 조회되는 값은 성능을 위해 중복 저장할 수 있습니다.
핵심은 의도를 알고 저장하는 것입니다. 이 값이 원본인지, 스냅샷인지, 성능을 위한 중복인지 구분할 수 있어야 합니다. 그 구분이 가능해질 때 데이터 모델링은 단순한 테이블 작성이 아니라 서비스의 구조를 설계하는 일이 됩니다.
이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.