데이터베이스 설계 단계: 요구사항·ERD·논리·물리 설계와 변경 배포
1장. “같은 상품을 두 줄로 담게 해주세요”라는 작은 요구#
쇼핑몰 주문 시스템이 이미 운영 중입니다.
현재 주문품목의 식별 규칙은 다음과 같습니다.
주문ID + 상품ID즉 하나의 주문에서 같은 상품은 한 줄만 존재한다고 가정했습니다.
예:
주문 501
상품 11
수량 3여기까지는 문제가 없습니다.
그런데 새로운 요구가 들어옵니다.
같은 상품이라도 배송일이 다르면 주문에서 두 줄로 나눌 수 있게 해주세요.
예:
주문 501
품목 1
상품 11
수량 2
배송일 9월 2일
주문 501
품목 2
상품 11
수량 1
배송일 9월 5일기존 키:
(주문ID, 상품ID)로는 두 행을 구분할 수 없습니다.
이 요구는 단순한 화면 변경처럼 보이지만 실제로는 다음을 모두 건드립니다.
업무 규칙
ERD
후보키
기본키
외래키
환불 구조
배송 구조
API
기존 데이터
인덱스
배포 순서
롤백 방법데이터베이스 설계는 ERD 한 장으로 끝나는 작업이 아닙니다.
2장. 데이터베이스 설계의 핵심은 단계 이름을 외우는 것이 아니다#
보통 데이터베이스 설계는 다음과 같은 흐름으로 설명합니다.
요구사항 분석
↓
개념 설계
↓
논리 설계
↓
물리 설계
↓
구현·검증
↓
운영·변경하지만 중요한 것은:
몇 단계인가?가 아닙니다.
더 중요한 질문은:
한 업무 요구가 각 단계에서 어떤 결정으로 구체화되는가?
입니다.
3장. 같은 요구가 단계마다 다른 형태로 표현된다#
“같은 상품 두 줄 허용”이라는 요구를 예로 보겠습니다.
요구사항 분석#
같은 상품이라도 배송 조건이 다르면
한 주문에서 여러 품목으로 존재할 수 있다.개념 설계#
주문
1:N
주문품목
상품
1:N
주문품목논리 설계#
주문품목 식별자
=
주문ID + 품목번호물리 설계#
PRIMARY KEY (
order_id,
item_no
)구현#
기존 행에 item_no 채우기
환불 참조 변경
새 API 적용운영#
구버전 앱 종료
기존 키 제거
새 기능 활성같은 규칙이 단계마다 다른 구체성으로 바뀝니다.
4장. 단계별 역할을 한눈에 보면#
| 단계 | 핵심 질문 | 대표 산출물 |
|---|---|---|
| 요구사항 분석 | 무엇을 저장해야 하는가 | 용어집, 업무 규칙, 사례 |
| 개념 설계 | 어떤 대상과 관계가 있는가 | ERD, 개체·관계 정의 |
| 논리 설계 | 어떤 속성과 키로 표현할 것인가 | 관계 스키마, PK/FK, 정규화 |
| 물리 설계 | 실제 DB에서 어떻게 저장·조회할 것인가 | DDL, 타입, 인덱스, 파티션 |
| 구현·검증 | 실제 데이터에서 규칙이 작동하는가 | 마이그레이션, 테스트 결과 |
| 운영·변경 | 변경을 어떻게 안전하게 반영할 것인가 | 배포 계획, 백필, 롤백 |
5장. 설계는 폭포처럼 한 번 지나가고 끝나는 과정이 아니다#
현실에서는 다음처럼 움직입니다.
flowchart LR
A["요구사항"] --> B["개념 모델"]
B --> C["논리 모델"]
C --> D["물리 설계"]
D --> E["구현·운영"]
E --> F["새 요구·장애·성능 문제"]
F --> A운영 중 요구가 바뀌면 다시 모델을 검토합니다.
즉 데이터베이스 설계는:
프로젝트 초기에 한 번 수행하는 것이 아니라:
시스템 생명주기 전체에서 반복되는 활동입니다.
6장. 요구사항 분석 — 먼저 업무 사건을 정의한다#
주문 시스템에서 중요한 것은 테이블 이름이 아닙니다.
먼저 실제 사건을 확인합니다.
예:
고객이 주문한다.
주문에는 여러 품목이 있다.
같은 상품이 여러 품목으로 존재할 수 있다.
품목별 배송일이 다를 수 있다.
품목 일부만 취소할 수 있다.
품목 일부만 환불할 수 있다.이런 문장이 모델의 출발점입니다.
7장. 요구사항은 명사보다 행동과 예외를 확인해야 한다#
초기 회의에서는 흔히 다음 명사가 나옵니다.
고객
주문
상품
배송
환불이것만으로는 설계가 충분하지 않습니다.
다음과 같은 질문이 더 중요합니다.
같은 상품을 두 번 담을 수 있는가?
한 품목을 나눠 배송할 수 있는가?
수량 일부만 취소할 수 있는가?
환불이 여러 번 발생할 수 있는가?
상품 가격이 바뀌면 과거 주문 가격도 바뀌는가?8장. 요구사항은 정상 사례보다 예외 사례에서 더 많이 드러난다#
정상 주문:
상품 A 1개
상품 B 1개만 테스트하면 많은 모델이 정상처럼 보입니다.
하지만 다음 사례를 넣어야 합니다.
같은 상품 두 줄
부분 취소
부분 환불
배송 분리
가격 변경 후 과거 주문 조회
중복 주문 요청예외 데이터가 모델의 약점을 드러냅니다.
9장. 요구사항을 검증 가능한 문장으로 바꾸자#
나쁜 요구:
주문을 유연하게 관리한다.좋은 요구:
한 주문에서 같은 상품은
배송일이 다르면 두 개 이상의 품목으로 존재할 수 있다.더 좋은 형태:
주문 501에 상품 11을
품목 1과 품목 2로 각각 저장할 수 있어야 한다.이 정도가 되면 실제 데이터로 검증할 수 있습니다.
10장. 개념 설계 — 업무 대상을 관계로 표현한다#
요구사항에서 다음 대상을 찾았습니다.
고객
주문
주문품목
상품관계는:
erDiagram
CUSTOMER ||--o{ ORDER_HEADER : places
ORDER_HEADER ||--|{ ORDER_ITEM : contains
PRODUCT ||--o{ ORDER_ITEM : referenced_by
CUSTOMER {
bigint customer_id PK
}
ORDER_HEADER {
bigint order_id PK
bigint customer_id FK
}
ORDER_ITEM {
bigint order_id FK
integer item_no
bigint product_id FK
integer quantity
}
PRODUCT {
bigint product_id PK
}개념 설계에서는 구현 제품의 자료형보다 업무 관계가 핵심입니다.
11장. 주문과 상품의 M:N 관계를 품목이 해소한다#
주문 하나에는 여러 상품이 있습니다.
상품 하나도 여러 주문에 등장합니다.
따라서:
주문
N:M
상품관계입니다.
이를 주문품목이 해소합니다.
주문
1:N
주문품목
N:1
상품입니다.
12장. 그러나 “중간 테이블 만들기”만으로 설계가 끝나지는 않는다#
주문품목에:
order_id
product_id만 있다고 하겠습니다.
이 구조는:
한 주문에서 같은 상품 한 번이라는 업무 규칙에는 맞을 수 있습니다.
하지만:
같은 상품 두 줄을 허용하지 못합니다.
관계는 맞아도 식별 규칙이 부족한 것입니다.
13장. 논리 설계 — 한 행이 무엇인지 먼저 정의한다#
주문품목 테이블의 한 행은 무엇일까요?
이번 업무에서는:
한 주문 안의 하나의 품목 줄
입니다.
그러면 한 행을 식별하는 키가 필요합니다.
예:
order_id
item_no입니다.
14장. 기존 키의 문제#
기존 기본키:
(order_id, product_id)라고 하겠습니다.
주문 501에서 상품 11을 두 줄로 저장하면:
501 / 11
501 / 11이 됩니다.
기본키 충돌이 발생합니다.
15장. 새 키를 정의한다#
다음처럼 바꿀 수 있습니다.
(order_id, item_no)예:
501 / 1 / 상품11
501 / 2 / 상품11입니다.
같은 상품이라도 품목번호가 다르므로 구분할 수 있습니다.
16장. product_id는 식별자가 아니라 품목의 속성이 된다#
새 구조:
order_id
item_no
product_id
quantity입니다.
이제:
order_id + item_no가 품목을 식별합니다.
product_id는 어떤 상품인지 알려주는 참조입니다.
17장. 주문 당시 가격도 상품 현재 가격과 분리해야 한다#
상품 마스터:
product_id = 11
current_price = 1500주문 당시 가격:
unit_price = 1200이라고 하겠습니다.
주문 금액을 현재 상품 가격으로 다시 계산하면 과거 매출이 바뀝니다.
따라서 주문품목에는:
unit_price같은 거래 당시 값을 저장해야 할 수 있습니다.
18장. 현재 기준값과 거래 사실은 다른 데이터다#
상품 가격:
현재 판매 기준주문 단가:
과거 거래에 실제 적용된 값입니다.
둘을 구분하지 않으면:
가격 변경
→ 과거 주문 금액 변경이라는 문제가 생깁니다.
19장. 환불도 품목을 정확히 가리켜야 한다#
같은 상품이 두 줄 존재합니다.
품목 1
상품 11
배송 9월 2일
품목 2
상품 11
배송 9월 5일품목 2만 환불한다고 하겠습니다.
환불 테이블이:
order_id
product_id만 가지고 있다면 어느 줄인지 구분할 수 없습니다.
20장. 환불 참조에도 새로운 식별자가 전달되어야 한다#
예:
refund
order_id
item_no
refund_qty
refund_amount처럼 만들 수 있습니다.
즉 주문품목의 키 변경은:
주문품목 테이블 하나에서 끝나지 않습니다.
연결된 모든 참조가 영향을 받습니다.
21장. 논리 모델에서 함수 종속을 확인하자#
주문품목에서:
(order_id, item_no)
→ product_id
→ quantity
→ unit_price가 아니라 정확하게는:
(order_id, item_no)
→ product_id, quantity, unit_price같은 종속을 생각할 수 있습니다.
기본키가 행 전체를 결정하는 구조인지 확인합니다.
22장. product_id가 quantity를 결정하는 것은 아니다#
같은 상품이라도 주문마다 수량은 다릅니다.
따라서:
product_id → quantity는 성립하지 않습니다.
상품 자체 속성과 거래 속성을 구분해야 합니다.
23장. 논리 설계에서 정규화는 중복 제거가 목적의 전부가 아니다#
정규화는:
어떤 사실이 어떤 키에 종속되는가?를 분리하는 과정입니다.
예:
상품명
→ 상품 마스터
주문 수량
→ 주문품목
주문 당시 단가
→ 주문품목각 사실의 소속을 구분합니다.
24장. 물리 설계 — 실제 DBMS 구조로 옮긴다#
논리 모델:
주문품목
(order_id, item_no)을 PostgreSQL 구조로 옮기면 예를 들어 다음과 같습니다.
CREATE TABLE order_item (
order_id bigint NOT NULL,
item_no integer NOT NULL,
product_id bigint NOT NULL,
quantity integer NOT NULL,
unit_price numeric(12, 2) NOT NULL,
PRIMARY KEY (
order_id,
item_no
),
FOREIGN KEY (
order_id
)
REFERENCES orders(order_id),
FOREIGN KEY (
product_id
)
REFERENCES product(product_id),
CHECK (
quantity > 0
),
CHECK (
unit_price >= 0
)
);25장. 물리 설계에서는 자료형도 업무 의미를 반영해야 한다#
수량:
integer가격:
numeric주문 ID:
bigint등을 선택할 수 있습니다.
단순히:
숫자니까 int라고 고르는 것이 아니라:
최댓값
정밀도
연산 방식
외부 API 타입을 고려합니다.
26장. 금액에 부동소수점을 사용할 때는 주의해야 한다#
금액 계산에서는:
0.1 + 0.2같은 부동소수점 표현 문제가 중요할 수 있습니다.
정확한 소수 금액이 필요하다면:
numeric계열을 검토합니다.
업무 통화와 소수 자리수에 맞게 정해야 합니다.
27장. 물리 설계에는 인덱스도 포함된다#
대표 조회:
고객 7의 최근 주문을 조회한다.
라고 하겠습니다.
조건:
WHERE customer_id = ?
ORDER BY ordered_at DESC
LIMIT 20이라면 다음과 같은 인덱스를 검토할 수 있습니다.
CREATE INDEX orders_customer_date_idx
ON orders (
customer_id,
ordered_at DESC
);28장. 인덱스는 업무 조회 패턴에서 나온다#
인덱스를 먼저 만들고 쿼리를 맞추는 것이 아니라:
자주 실행하는 SQL
↓
필터
↓
정렬
↓
조인
↓
인덱스 후보순으로 생각하는 것이 자연스럽습니다.
29장. 모든 외래키 컬럼에 자동으로 필요한 인덱스가 생긴다고 가정하지 말자#
DBMS마다 FK와 인덱스의 처리 방식은 다릅니다.
외래키가 있다고 해서:
조회용 최적 인덱스가 자동으로 완성된다고 생각하면 안 됩니다.
실제 조인·삭제·갱신 패턴을 보고 별도 인덱스를 검토합니다.
30장. 물리 설계에서는 용량도 추정해야 한다#
예:
일 주문
100만 건
주문당 품목
평균 4개라면:
일 주문품목
약 400만 행입니다.
1년 단순 계산:
400만 × 365
≈ 14억 6천만 행입니다.
이 정도 규모라면:
인덱스 크기
파티션
백업
보존
아카이브까지 검토해야 할 수 있습니다.
31장. 설계 도구는 판단을 대신하지 않는다#
모델링 도구를 사용하면:
ERD 작성
DDL 생성
리버스 엔지니어링
모델 비교를 편하게 할 수 있습니다.
하지만 도구가:
이 기본키가 실제 업무에 맞는가?를 자동으로 판단하지는 못합니다.
32장. 리버스 엔지니어링도 실제 모델 검증이 필요하다#
기존 DB를 모델링 도구로 읽었습니다.
확인해야 합니다.
PK가 모두 들어왔는가?
FK가 빠지지 않았는가?
CHECK 제약이 표현되는가?
인덱스가 구분되는가?
제품 고유 타입이 제대로 표현되는가?도구가 읽어 왔다는 사실만으로 완전성을 보장할 수 없습니다.
33장. DDL 생성도 실행 결과로 검증해야 한다#
모델링 도구가 생성한 DDL이:
문법상 맞는가?
대상 DBMS 버전에서 실행되는가?
원하는 제약을 만드는가?
의도하지 않은 객체를 삭제하지 않는가?를 확인해야 합니다.
특히 자동 생성된 변경 DDL을 운영에서 바로 실행하는 것은 위험할 수 있습니다.
34장. 구현 단계 — 작은 입력으로 업무 규칙을 검증한다#
새 모델을 만들었습니다.
이제 실제 입력으로 확인합니다.
검수 데이터:
주문 501
품목 1
상품 11
배송일 9월 2일
품목 2
상품 11
배송일 9월 5일둘 다 저장되어야 합니다.
35장. 첫 번째 검증 — 같은 상품 두 줄 저장#
예:
INSERT INTO order_item (
order_id,
item_no,
product_id,
quantity,
unit_price
)
VALUES
(501, 1, 11, 2, 1200),
(501, 2, 11, 1, 1200);두 행이 정상 저장되어야 합니다.
기존 키였다면 충돌했을 수 있습니다.
36장. 두 번째 검증 — 품목 2만 취소할 수 있어야 한다#
취소 요청:
order_id = 501
item_no = 2입니다.
결과:
품목 1
유지
품목 2
취소이어야 합니다.
37장. 세 번째 검증 — 기존 주문도 그대로 조회되어야 한다#
새 모델이 신규 주문만 지원하고 기존 주문 조회가 깨지면 안 됩니다.
따라서:
기존 데이터
새 데이터를 함께 테스트해야 합니다.
38장. 스키마 변경은 데이터베이스만 바꾸는 작업이 아니다#
기존 API:
/order/{orderId}/product/{productId}로 품목을 수정했다고 하겠습니다.
새 모델에서는 같은 상품이 두 줄 존재할 수 있습니다.
그러면:
orderId + productId만으로는 품목이 하나로 정해지지 않습니다.
39장. 새 API는 품목 식별자를 사용해야 한다#
예:
/order/{orderId}/item/{itemNo}처럼 변경할 수 있습니다.
이제:
501 / 1
501 / 2를 명확히 구분할 수 있습니다.
40장. DB 기본키만 바꾸고 API를 그대로 두면 설계가 반쪽짜리다#
DB에서는:
(order_id, item_no)가 유일합니다.
하지만 API는 계속:
(order_id, product_id)로 품목을 찾습니다.
같은 상품 두 줄이 들어오면 API에서 대상이 모호해집니다.
즉:
DB 모델
성공
업무 기능
실패가 됩니다.
41장. 기존 데이터를 어떻게 새 키로 옮길지도 설계해야 한다#
기존 주문에는:
item_no가 없습니다.
모든 주문에서 상품이 한 번씩만 존재했다면:
주문별
ROW_NUMBER등으로 품목번호를 만들 수 있습니다.
하지만 이미 중복 상품이 존재하는 데이터가 있다면 별도 기준이 필요합니다.
42장. 백필 규칙도 업무 규칙이다#
예:
주문 내 생성 순서대로 item_no 부여라고 정할 수 있습니다.
하지만 생성 시각이 없는 경우:
어떤 순서가 원래 화면 순서인가?를 알 수 없을 수 있습니다.
데이터 마이그레이션은 단순 기술 작업이 아니라 의미 복원 작업이 될 수 있습니다.
43장. 기존 환불 참조도 함께 옮겨야 한다#
기존 환불:
order_id
product_id라고 하겠습니다.
새 구조:
order_id
item_no로 바꾸려면 기존 환불이 어느 품목을 가리키는지 매핑해야 합니다.
기존 주문에 같은 상품이 한 번뿐이었다면 자동 매핑이 가능할 수 있습니다.
하지만 중복 상품이 이미 있었다면 모호할 수 있습니다.
44장. 스키마 변경은 Expand → Migrate → Contract로 나눌 수 있다#
무중단에 가까운 변경에서는 흔히 다음 흐름을 사용합니다.
확장
↓
데이터 이행
↓
애플리케이션 전환
↓
축소이를:
Expand
Migrate
Contract관점으로 볼 수 있습니다.
45장. Expand 단계에서는 기존 구조를 깨지 않는다#
예를 들어 phone을 contact_phone으로 바꾸려고 합니다.
기존 열을 바로 이름 변경하면 구버전 애플리케이션이 실패할 수 있습니다.
먼저:
ALTER TABLE customer
ADD COLUMN contact_phone text;처럼 새 열을 추가합니다.
기존 phone은 유지합니다.
46장. 데이터 백필을 수행한다#
UPDATE customer
SET contact_phone = phone
WHERE contact_phone IS NULL;대형 테이블이라면 한 번의 대규모 UPDATE가:
긴 트랜잭션
WAL 증가
락
복제 지연을 만들 수 있으므로 작은 배치로 실행하는 방식을 검토합니다.
47장. 두 열의 값이 같은지 검증한다#
SELECT COUNT(*)
FROM customer
WHERE phone
IS DISTINCT FROM
contact_phone;결과:
0을 기대합니다.
IS DISTINCT FROM은 NULL까지 포함해 두 값을 비교하는 데 유용합니다.
48장. 새 코드와 옛 코드가 동시에 존재하는 기간이 가장 어렵다#
구버전:
phone 쓰기신버전:
contact_phone 쓰기를 한다고 하겠습니다.
동기화가 없다면 두 값이 바로 달라질 수 있습니다.
따라서 호환 기간에는:
애플리케이션 이중 쓰기
트리거
단일 쓰기 주체
동기화 작업등의 전략이 필요할 수 있습니다.
49장. 이중 쓰기는 생각보다 위험하다#
애플리케이션이:
phone UPDATE 성공
contact_phone UPDATE 실패하면 두 열이 달라집니다.
두 개를 하나의 DB 트랜잭션으로 처리할 수 있는 구조라면 원자성을 높일 수 있습니다.
외부 시스템까지 포함되면 더 복잡해집니다.
50장. Contract는 가장 늦게 수행한다#
다음 조건이 모두 확인됐다고 하겠습니다.
구버전 앱 종료
새 열 백필 완료
두 열 불일치 0
롤백 기간 종료그제야 기존:
phone열 제거를 검토할 수 있습니다.
51장. 키 변경도 같은 확장·이행·축소 전략을 적용할 수 있다#
기존:
(order_id, product_id)새 구조:
(order_id, item_no)입니다.
처음부터 기존 PK를 바로 제거하면 구버전 코드가 깨질 수 있습니다.
52장. 첫 단계는 item_no를 추가하는 것이다#
order_item
order_id
product_id
item_no가 공존하는 기간을 만듭니다.
기존 행에는 item_no를 백필합니다.
53장. 새 식별 규칙의 유일성을 먼저 검증한다#
예:
(order_id, item_no)에 중복이 없는지 확인합니다.
그다음 새 UNIQUE 또는 PK 후보를 준비합니다.
54장. 참조 테이블도 새 키를 받을 준비를 해야 한다#
환불:
refund배송:
shipment_item등이 주문품목을 참조한다면:
item_no를 추가하고 매핑합니다.
DB 하나만 바꾸는 것이 아니라 관계 전체를 이동합니다.
55장. 새 애플리케이션을 먼저 배포할 수도 있다#
신버전 애플리케이션은:
item_no를 이해합니다.
구버전은:
product_id만 이해합니다.
이 기간에는 어떤 기능까지 활성화할 것인지가 중요합니다.
56장. 새 스키마가 준비됐다고 새 기능을 즉시 켜면 안 될 수 있다#
구버전 애플리케이션이 아직 살아 있습니다.
그런데:
같은 상품 두 줄 입력기능을 바로 활성화했습니다.
구버전은:
order_id + product_id로 품목을 수정합니다.
이제 두 행을 구분할 수 없습니다.
57장. 스키마 배포와 기능 활성 시점을 분리해야 한다#
예:
1. 새 컬럼 배포
2. 기존 데이터 백필
3. 새 API 배포
4. 모든 클라이언트 전환
5. 같은 상품 두 줄 기능 활성
6. 구식 키 제거처럼 순서를 나눌 수 있습니다.
데이터베이스 변경이 끝났다고 업무 기능을 즉시 열어야 하는 것은 아닙니다.
58장. 롤백 가능 시점도 미리 정해야 한다#
새 구조만 배포한 상태에서는 쉽게 되돌릴 수 있습니다.
하지만 아직 새 형태의 데이터는 없습니다.
예:
같은 상품 한 줄만 존재입니다.
이때는 이전 키로 복귀하기 쉽습니다.
59장. 같은 상품 두 줄이 실제로 저장된 뒤에는 롤백이 어려워진다#
새 데이터:
501 / 품목1 / 상품11
501 / 품목2 / 상품11가 들어왔습니다.
이제 기존 제약:
UNIQUE (
order_id,
product_id
)를 복원할 수 없습니다.
두 행이 충돌하기 때문입니다.
60장. 롤백은 DDL을 되돌리는 것이 전부가 아니다#
두 품목을 하나로 합친다고 하겠습니다.
그러면:
배송일이 서로 다름
수량이 서로 다름
환불 상태가 서로 다름
단가가 서로 다를 수 있음문제가 발생합니다.
즉 데이터 의미 자체가 이전 구조로 돌아갈 수 없게 됩니다.
61장. 따라서 데이터 변경에는 비가역 경계를 정의해야 한다#
예:
새 구조 배포 전
→ 완전 롤백 가능
백필 후
→ 데이터 변환 롤백 필요
새 기능 활성 전
→ 구버전 복귀 가능
같은 상품 두 줄 입력 후
→ 단순 롤백 불가처럼 기록할 수 있습니다.
62장. 스키마 버전과 애플리케이션 버전을 함께 봐야 한다#
예:
DB v4가 적용됐습니다.
하지만 애플리케이션:
API v2
배치 v1
관리자 v3가 동시에 사용할 수 있습니다.
DB 변경은 모든 소비자가 안전한지 확인해야 합니다.
63장. 직접 SQL을 사용하는 보고서도 영향을 받는다#
애플리케이션만 검색하면 안 됩니다.
예:
BI 보고서
ETL
배치
운영 스크립트
엑셀 ODBC
외부 연계가 기존:
order_id + product_id를 사용하고 있을 수 있습니다.
64장. 데이터베이스 변경 영향 분석의 핵심은 참조 그래프다#
하나의 컬럼 변경은 다음처럼 이어질 수 있습니다.
flowchart TD
A["order_item PK 변경"] --> B["refund FK"]
A --> C["shipment FK"]
A --> D["API"]
A --> E["배치"]
A --> F["보고서"]
A --> G["인덱스"]
A --> H["캐시 키"]이 연결을 찾는 것이 변경 영향 분석입니다.
65장. 요구사항과 테스트를 직접 연결하자#
요구:
같은 상품 두 줄 허용테스트:
같은 order_id
같은 product_id
다른 item_no두 행 INSERT 성공
입니다.
66장. 부분 취소 요구도 테스트로 연결한다#
요구:
둘째 품목만 취소 가능테스트:
item_no = 2
취소
item_no = 1
유지입니다.
67장. 과거 주문 보존 요구도 테스트한다#
기존 주문:
item_no 백필 완료
기존 환불 참조 정상
기존 조회 결과 동일인지 확인합니다.
새 기능만 테스트하면 마이그레이션 오류를 놓칠 수 있습니다.
68장. 설계 산출물은 서로 연결되어야 한다#
좋은 프로젝트에서는 다음이 연결됩니다.
REQ-ORDER-017
같은 상품 두 줄 허용↓
ERD
OrderItem 식별 관계 변경↓
논리 모델
(order_id, item_no)↓
DDL
PK 변경↓
API
itemNo 추가↓
TEST-ORDER-231
같은 상품 두 줄 저장 성공이렇게 추적할 수 있습니다.
69장. 요구사항 추적이 중요한 이유#
몇 달 뒤 새로운 담당자가:
왜 product_id를 기본키에서 뺐습니까?
라고 물을 수 있습니다.
변경 근거가 없다면:
누가 그렇게 했는지 모름이 됩니다.
반면:
REQ-ORDER-017이 연결돼 있다면 설계 이유를 바로 이해할 수 있습니다.
70장. 정보공학 방법론과 프로젝트 설계 단계를 억지로 1:1 매핑할 필요는 없다#
정보공학은 전사 정보 전략과 시스템 구축을 더 넓은 범위에서 설명하는 방법론입니다.
예:
정보 전략 계획
업무 영역 분석
시스템 설계
구축등의 수준으로 설명할 수 있습니다.
반면 현재 글의:
요구사항
개념
논리
물리
구현
운영은 데이터베이스 설계 활동을 좀 더 구체적으로 나눈 것입니다.
추상 수준이 다릅니다.
71장. 단계 수보다 각 단계의 질문이 중요하다#
조직 A:
5단계조직 B:
7단계를 사용해도 괜찮습니다.
중요한 것은 다음 질문이 빠지지 않는 것입니다.
업무 규칙은 정의됐는가?
관계가 표현됐는가?
키가 정해졌는가?
물리 구조가 정해졌는가?
실제로 검증됐는가?
변경 절차가 있는가?72장. 모델링 도구를 평가할 때 볼 항목#
대표적으로 확인할 수 있습니다.
| 기능 | 검증 질문 |
|---|---|
| 역공학 | 기존 PK·FK·제약이 빠짐없이 들어오는가 |
| 모델 비교 | 변경 영향이 명확하게 보이는가 |
| DDL 생성 | 대상 DBMS에서 실제 실행되는가 |
| 명명 규칙 | 표준 용어 적용이 가능한가 |
| 버전 관리 | 변경 이력을 추적할 수 있는가 |
| 협업 | 승인·리뷰 흐름이 가능한가 |
73장. 도구 기능이 많다고 좋은 모델이 만들어지는 것은 아니다#
아무리 좋은 모델링 도구를 사용해도:
같은 상품 두 줄이라는 업무 요구를 놓치면 기존 키를 그대로 사용할 수 있습니다.
도구는:
표현
비교
자동화를 도와줍니다.
판단은 사람이 해야 합니다.
74장. 설계 변경에서 흔한 실패 1 — ERD만 수정한다#
ERD:
item_no 추가완료.
하지만 DB:
기존 PK 유지API:
product_id로 품목 식별이면 실제 서비스는 바뀌지 않았습니다.
75장. 실패 2 — DB만 수정한다#
DB:
새 PK 적용성공.
하지만 환불 서비스:
order_id + product_id사용.
같은 상품 두 줄에서 어떤 품목을 환불할지 모호합니다.
76장. 실패 3 — 신규 데이터만 테스트한다#
새 주문:
정상입니다.
하지만 기존 주문의 item_no 백필이 잘못돼:
환불 연결 실패가 발생할 수 있습니다.
마이그레이션에서는 기존 데이터가 중요합니다.
77장. 실패 4 — 구버전 앱이 남아 있는데 기능부터 켠다#
DB와 신버전은 새 키를 이해합니다.
하지만 오래된 모바일 앱이 아직 운영 중입니다.
이 앱이 두 줄 주문을 수정하면 잘못된 행을 건드릴 수 있습니다.
기능 활성은 클라이언트 전환 상태와 함께 결정해야 합니다.
78장. 실패 5 — 롤백을 DDL만 생각한다#
새 컬럼을 DROP하면 예전 구조가 되었다고 생각합니다.
하지만 이미 새 업무 의미를 가진 데이터가 들어왔습니다.
동일 상품 두 줄
개별 환불
개별 배송이 데이터는 예전 구조로 단순 변환할 수 없습니다.
79장. 설계 품질을 검증하는 좋은 질문#
요구#
예외 사례가 정의됐는가?개념#
업무 사건이 빠지지 않았는가?논리#
한 행을 유일하게 식별할 수 있는가?물리#
실제 조회·저장 규모를 감당하는가?구현#
예외 데이터를 실제로 넣어봤는가?운영#
구버전과 신버전이 함께 존재할 때 안전한가?80장. 설계 단계별 체크리스트 — 요구사항#
- 핵심 업무 사건은 무엇인가?
- 정상 사례와 예외 사례가 있는가?
- 같은 상품 반복 같은 경계 조건을 확인했는가?
- 부분 취소·부분 환불이 가능한가?
- 과거 거래값을 보존해야 하는가?
- 어떤 데이터가 변경 가능한가?
- 어떤 사실은 삭제해서는 안 되는가?
- 중복 요청을 어떻게 처리하는가?
- 데이터 보존 기간은 얼마인가?
- 요구를 실제 예제로 검증할 수 있는가?
81장. 설계 단계별 체크리스트 — 개념 모델#
- 주요 개체가 빠지지 않았는가?
- 관계의 방향과 다중성이 맞는가?
- M:N 관계를 식별했는가?
- 주문품목 같은 업무 사건을 독립 개체로 볼 필요가 있는가?
- 약한 개체가 존재하는가?
- 관계 자체에 속성이 필요한가?
- 과거 이력을 개념 모델에 반영해야 하는가?
- 삭제 시 관계 의미가 어떻게 되는가?
- 같은 개체를 여러 시스템이 다른 의미로 정의하지 않는가?
- 업무 담당자가 ERD의 의미를 이해할 수 있는가?
82장. 설계 단계별 체크리스트 — 논리 모델#
- 후보키는 무엇인가?
- 기본키가 실제 업무 식별 규칙과 맞는가?
- 같은 상품 두 줄 같은 사례에서 키 충돌이 없는가?
- 외래키가 필요한 관계가 모두 표현됐는가?
- 함수 종속을 검토했는가?
- 정규화 근거가 있는가?
- 반정규화가 있다면 이유가 있는가?
- 거래 시점 값과 현재 마스터 값을 구분했는가?
- NULL의 의미가 정의되어 있는가?
- 코드와 상태의 의미가 정의되어 있는가?
83장. 설계 단계별 체크리스트 — 물리 설계#
- 자료형 크기가 적절한가?
- 금액 정밀도가 충분한가?
- 대표 SQL이 정의되어 있는가?
- 인덱스가 실제 접근 패턴과 맞는가?
- 예상 데이터량을 계산했는가?
- 파티셔닝이 필요한가?
- 보존·삭제 전략이 있는가?
- PK·FK·UNIQUE·CHECK가 실제 DDL로 구현됐는가?
- 제품 고유 기능에 대한 의존성을 알고 있는가?
- 변경 DDL의 잠금과 실행 시간을 검증했는가?
84장. 구현·마이그레이션 체크리스트#
- 기존 데이터에 새 키를 어떻게 채울 것인가?
- 백필 순서는 무엇인가?
- 백필 중 서비스가 쓰기를 계속하는가?
- 구버전과 신버전이 동시에 동작하는가?
- 이중 쓰기 실패 가능성이 있는가?
- 기존 FK 참조를 모두 옮겼는가?
- 신규·기존 데이터를 모두 테스트했는가?
- 데이터 건수와 합계를 전후 비교했는가?
- 변경 후 고아 참조가 없는가?
- 기능 활성 시점이 따로 정의되어 있는가?
85장. 배포·롤백 체크리스트#
- Expand 단계가 정의되어 있는가?
- 백필 완료 기준은 무엇인가?
- 애플리케이션 전환 순서는 무엇인가?
- 이전 API는 언제 종료되는가?
- 새 업무 기능은 언제 활성화하는가?
- 어떤 시점까지 롤백 가능한가?
- 새 데이터 입력 뒤에도 롤백 가능한가?
- 롤백 시 데이터 의미가 손실되지 않는가?
- 관측 지표와 오류 알림이 있는가?
- 기존 구조 제거 기준이 명확한가?
86장. 좋은 설계 문서는 그림보다 결정 근거가 중요하다#
ERD만 보면:
현재 구조는 알 수 있습니다.
하지만:
왜 이런 키를 사용했는가?
왜 이 테이블을 분리했는가?
왜 이 컬럼을 추가했는가?는 알기 어렵습니다.
그래서 설계 문서에는 결정 이유가 함께 있어야 합니다.
87장. 설계 결정 기록을 간단하게 남길 수 있다#
예:
결정
주문품목 PK를
(order_id, item_no)로 변경
이유
동일 상품을 서로 다른 배송 일정으로
여러 품목에 저장해야 함
대안
(order_id, product_id, delivery_date)
기각 이유
같은 배송일에도 프로모션·단가 차이로
여러 품목이 존재할 수 있음
영향
환불·배송·API·인덱스 변경 필요이런 기록이 나중에 매우 유용합니다.
88장. 기술적인 키와 업무 식별자를 분리할 수도 있다#
주문품목에:
order_item_id라는 단일 기술키를 둘 수도 있습니다.
예:
order_item_id = 90001그리고 업무상:
order_id + item_no에 UNIQUE를 둘 수 있습니다.
89장. 단일 기술키가 있다고 업무 유일성이 사라지는 것은 아니다#
예:
order_item_id
PK만 있고:
(order_id, item_no)의 UNIQUE가 없다면 같은 품목번호가 중복될 수 있습니다.
기술키와 업무 제약은 역할이 다릅니다.
90장. 키 설계에는 참조 편의성과 업무 의미를 함께 고려한다#
단일 ID:
order_item_id는 FK 참조가 간단합니다.
복합 키:
order_id + item_no는 주문 내 위치라는 업무 의미가 명확합니다.
둘 중 하나만 정답인 것은 아닙니다.
시스템 특성에 따라:
PK
order_item_id
UNIQUE
(order_id, item_no)처럼 둘 다 사용할 수 있습니다.
91장. 설계 완료의 기준은 ERD가 아니라 재현 가능한 업무 결과다#
최종 검증을 생각해 보겠습니다.
입력:
주문 501
상품 11
품목 1
배송 9월 2일
품목 2
배송 9월 5일기대:
두 품목 모두 저장
품목 2만 취소 가능
품목 1 유지
각 배송 정보 유지
환불도 품목별 참조입니다.
이 결과가 실제로 만들어져야 새 설계가 요구를 만족합니다.
92장. 설계 생명주기를 하나의 흐름으로 보면#
flowchart TD
A["업무 요구"] --> B["개념 모델"]
B --> C["논리 모델"]
C --> D["물리 구조"]
D --> E["DDL·데이터 이행"]
E --> F["애플리케이션 전환"]
F --> G["기능 활성"]
G --> H["운영 관측"]
H --> I["새 요구·문제 발견"]
I --> A설계와 운영을 분리된 세계로 보면 변경이 위험해집니다.
93장. 핵심 정리#
데이터베이스 설계는:
요구사항
→ ERD
→ 테이블정도로 끝나는 작업이 아닙니다.
하나의 업무 요구가:
모델
키
제약
SQL
API
기존 데이터
배포
롤백까지 이어지는 전체 과정입니다.
이번 사례의 시작은 단순했습니다.
같은 상품을 한 주문에서 두 줄로 담을 수 있게 해주세요.
하지만 이 한 문장은 기존:
(order_id, product_id)키가 더 이상 충분하지 않다는 사실을 드러냅니다.
그래서:
(order_id, item_no)같은 새로운 식별 규칙을 도입합니다.
그러면 환불·배송도 새 품목 식별자를 참조해야 합니다.
API도:
order_id + product_id가 아니라 품목 ID 또는 품목번호를 사용해야 합니다.
기존 데이터에는 새 식별자를 백필해야 합니다.
구버전 애플리케이션이 남아 있다면 새 스키마와 기존 스키마가 함께 동작하는 기간도 설계해야 합니다.
가장 중요한 것은 스키마 배포와 업무 기능 활성 시점을 구분하는 것입니다.
DB가 새 구조를 지원한다고 해서 구버전 앱이 남아 있는 상태에서 같은 상품 두 줄 기능을 바로 켤 수 있는 것은 아닙니다.
또 새 형태의 데이터가 실제로 들어온 이후에는 예전 구조로 단순 롤백할 수 없을 수도 있습니다.
같은 상품 두 줄
서로 다른 배송
각기 다른 환불이라는 정보는 예전 (주문ID, 상품ID) 구조로 압축하면 의미를 잃을 수 있기 때문입니다.
따라서 좋은 데이터베이스 설계는 다음 질문에 모두 답할 수 있어야 합니다.
이 요구는 왜 생겼는가?
개념 모델에서 어떻게 표현되는가?
한 행은 무엇을 의미하는가?
어떤 키가 그 행을 식별하는가?
실제 DB에서는 어떤 제약으로 구현되는가?
기존 데이터는 어떻게 옮기는가?
구버전과 신버전은 어떻게 공존하는가?
새 기능은 언제 활성화하는가?
어디까지 롤백할 수 있는가?결국 설계 단계의 목적은 산출물의 이름을 채우는 것이 아닙니다.
하나의 업무 결정이 요구사항에서 시작해 ERD·키·DDL·애플리케이션·기존 데이터·배포 결과까지 같은 의미로 유지되는지를 추적하는 것
입니다.
좋은 ERD는 그림이 아름다운 ERD가 아닙니다.
실제 업무의 예외를 저장할 수 있고, 요구가 바뀌었을 때 무엇을 함께 바꿔야 하는지 설명할 수 있으며, 그 변경을 운영 데이터에 안전하게 전달할 수 있는 모델입니다.