OPEN HYPER STEP
← 목록으로 (stack-analysis)
STACK-ANALYSIS · 45 / 90
stack-analysis
CHAPTER 45 / 90
읽기 약 2
FUNCTION

DB 설계: 정규화 vs 비정규화 판단


핵심 개념

1NF~3NF·denormalization·CQRS·실전 trade-off — 읽기 vs 쓰기 최적화.

본문

정규화 단계

📋 코드 (11줄)
1NF (1 정규화):
- 원자 값 (배열·객체 X)
- 각 행 고유 식별자

2NF:
- 1NF + 부분 함수 종속 제거
- 복합 키의 일부에만 의존하는 컬럼 X

3NF:
- 2NF + 이행 함수 종속 제거
- 비키 컬럼 → 비키 컬럼 의존 X

정규화 예시

SQL📋 코드 (41줄)
-- ❌ 비정규화 (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)
);

의도적 비정규화

SQL📋 코드 (29줄)
-- 정규화 + 자주 쓰는 집계는 캐싱
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 — 읽기/쓰기 분리

📋 코드 (10줄)
Command (쓰기) — 정규화 DB
- 트랜잭션 보장
- INSERT/UPDATE/DELETE
- PostgreSQL


Query (읽기) — 비정규화 (Materialized View)
- 빠른 읽기
- 복잡한 조회 미리 계산
- ElasticSearch / Redis / Materialized View
SQL📋 코드 (18줄)
-- 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;

정규화 결정 트리

📋 코드 (12줄)
쿼리 빈도 vs 변경 빈도?

읽기 >> 쓰기 → 비정규화 (캐시 컬럼·MV)
쓰기 ≈ 읽기 → 정규화 (3NF)
쓰기 >> 읽기 → 정규화 (이력 보존)


데이터 일관성 중요? → 정규화
성능이 우선? → 비정규화


분석 쿼리 자주? → 별도 OLAP DB (Snowflake·BigQuery)

시계열 — Hot/Warm/Cold

SQL📋 코드 (17줄)
-- 최근 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