stack-analysis
CHAPTER 45 / 90
읽기 약 2분
FUNCTION
DB 설계: 정규화 vs 비정규화 판단
핵심 개념
1NF~3NF·denormalization·CQRS·실전 trade-off — 읽기 vs 쓰기 최적화.
본문
정규화 단계
1NF (1 정규화):
- 원자 값 (배열·객체 X)
- 각 행 고유 식별자
2NF:
- 1NF + 부분 함수 종속 제거
- 복합 키의 일부에만 의존하는 컬럼 X
3NF:
- 2NF + 이행 함수 종속 제거
- 비키 컬럼 → 비키 컬럼 의존 X정규화 예시
-- ❌ 비정규화 (1NF 위반)
CREATE TABLE orders (
id INT PRIMARY KEY,
customer_name VARCHAR,
customer_email VARCHAR,
items JSONB, -- [{name, price}, ...]
total DECIMAL
);
-- 문제:
-- - customer_name 변경 시 모든 주문 수정
-- - items 검색 어려움
-- ✅ 정규화 (3NF)
CREATE TABLE customers (
id SERIAL PRIMARY KEY,
name VARCHAR NOT NULL,
email VARCHAR UNIQUE NOT NULL
);
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name VARCHAR NOT NULL,
price DECIMAL NOT NULL
);
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
customer_id INT REFERENCES customers(id),
total DECIMAL NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE TABLE order_items (
order_id INT REFERENCES orders(id),
product_id INT REFERENCES products(id),
quantity INT NOT NULL,
unit_price DECIMAL NOT NULL, -- 주문 시점 가격 (이력)
PRIMARY KEY (order_id, product_id)
);의도적 비정규화
-- 정규화 + 자주 쓰는 집계는 캐싱
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email VARCHAR UNIQUE,
name VARCHAR,
-- 비정규화 — 매번 COUNT 안 하게
posts_count INT DEFAULT 0,
followers_count INT DEFAULT 0,
last_post_at TIMESTAMP
);
-- 트리거로 자동 갱신
CREATE FUNCTION update_user_post_count() RETURNS trigger AS $$
BEGIN
IF TG_OP = 'INSERT' THEN
UPDATE users SET posts_count = posts_count + 1, last_post_at = NEW.created_at
WHERE id = NEW.author_id;
ELSIF TG_OP = 'DELETE' THEN
UPDATE users SET posts_count = posts_count - 1
WHERE id = OLD.author_id;
END IF;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_post_count
AFTER INSERT OR DELETE ON posts
FOR EACH ROW EXECUTE FUNCTION update_user_post_count();CQRS — 읽기/쓰기 분리
Command (쓰기) — 정규화 DB
- 트랜잭션 보장
- INSERT/UPDATE/DELETE
- PostgreSQL
Query (읽기) — 비정규화 (Materialized View)
- 빠른 읽기
- 복잡한 조회 미리 계산
- ElasticSearch / Redis / Materialized View-- Materialized View
CREATE MATERIALIZED VIEW user_stats AS
SELECT
u.id,
u.name,
COUNT(DISTINCT p.id) as post_count,
COUNT(DISTINCT f.id) as follower_count,
MAX(p.created_at) as last_post_at
FROM users u
LEFT JOIN posts p ON p.author_id = u.id
LEFT JOIN followers f ON f.followed_id = u.id
GROUP BY u.id, u.name;
CREATE UNIQUE INDEX idx_user_stats_id ON user_stats(id);
-- 갱신 (스케줄러)
REFRESH MATERIALIZED VIEW CONCURRENTLY user_stats;정규화 결정 트리
쿼리 빈도 vs 변경 빈도?
읽기 >> 쓰기 → 비정규화 (캐시 컬럼·MV)
쓰기 ≈ 읽기 → 정규화 (3NF)
쓰기 >> 읽기 → 정규화 (이력 보존)
데이터 일관성 중요? → 정규화
성능이 우선? → 비정규화
분석 쿼리 자주? → 별도 OLAP DB (Snowflake·BigQuery)시계열 — Hot/Warm/Cold
-- 최근 1주: PostgreSQL (트랜잭션)
-- 1주~3개월: PostgreSQL + 압축
-- 3개월+: S3 + Athena (비용 절감)
-- TimescaleDB (PostgreSQL 확장)
CREATE TABLE events (
time TIMESTAMPTZ NOT NULL,
user_id INT,
event_type VARCHAR,
data JSONB
);
SELECT create_hypertable('events', 'time');
-- 자동 chunk 분할 + 오래된 chunk 압축·archive
SELECT add_compression_policy('events', INTERVAL '7 days');
SELECT add_retention_policy('events', INTERVAL '6 months');다음 챕터
CH.46 "데이터 백업과 복구 전략".
AI 프롬프트
🤖 AI에게 잘 물어보는 법 — 모델·전략별 프롬프트
Claude
무료: Sonnet 4.6 / Pro $20/mo: Opus 4.6
내 코드의 DB 설계 부분을 분석해서 실전 분석 + 개선 우선순위를 알려줘.
ChatGPT
무료: GPT-5.5 / Plus $20/mo: GPT-5.5 Pro
DB 설계 관련 인기 라이브러리/패턴 5개를 비교 분석해서 패턴 추출를 알려줘.
Gemini
무료: 2.5 Flash / Pro $19.99/mo: 3.1 Pro
내 프로젝트 전체에서 DB 설계 최적화 가능 위치를 보고해줘.
Grok
무료: Grok 4.1 / SuperGrok $30/mo
2026년 한국 백엔드 시장의 DB 설계 트렌드를 솔직히 알려줘.
⭐ 이것만 기억하세요
DB 설계: 정규화 vs 비정규화 판단은 이 3가지만 확실히 잡으세요
1.3NF가 기본 — 트랜잭션·일관성 우선
2.읽기 빈도 높으면 비정규화 컬럼·Materialized View
3.CQRS = 쓰기(정규화) + 읽기(비정규화) 분리 — 큰 시스템 표준
공유하기
진행도 45 / 90