데이터베이스 설계 단계: 요구사항·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장. 설계 단계별 체크리스트 — 요구사항#

  1. 핵심 업무 사건은 무엇인가?
  2. 정상 사례와 예외 사례가 있는가?
  3. 같은 상품 반복 같은 경계 조건을 확인했는가?
  4. 부분 취소·부분 환불이 가능한가?
  5. 과거 거래값을 보존해야 하는가?
  6. 어떤 데이터가 변경 가능한가?
  7. 어떤 사실은 삭제해서는 안 되는가?
  8. 중복 요청을 어떻게 처리하는가?
  9. 데이터 보존 기간은 얼마인가?
  10. 요구를 실제 예제로 검증할 수 있는가?

81장. 설계 단계별 체크리스트 — 개념 모델#

  1. 주요 개체가 빠지지 않았는가?
  2. 관계의 방향과 다중성이 맞는가?
  3. M:N 관계를 식별했는가?
  4. 주문품목 같은 업무 사건을 독립 개체로 볼 필요가 있는가?
  5. 약한 개체가 존재하는가?
  6. 관계 자체에 속성이 필요한가?
  7. 과거 이력을 개념 모델에 반영해야 하는가?
  8. 삭제 시 관계 의미가 어떻게 되는가?
  9. 같은 개체를 여러 시스템이 다른 의미로 정의하지 않는가?
  10. 업무 담당자가 ERD의 의미를 이해할 수 있는가?

82장. 설계 단계별 체크리스트 — 논리 모델#

  1. 후보키는 무엇인가?
  2. 기본키가 실제 업무 식별 규칙과 맞는가?
  3. 같은 상품 두 줄 같은 사례에서 키 충돌이 없는가?
  4. 외래키가 필요한 관계가 모두 표현됐는가?
  5. 함수 종속을 검토했는가?
  6. 정규화 근거가 있는가?
  7. 반정규화가 있다면 이유가 있는가?
  8. 거래 시점 값과 현재 마스터 값을 구분했는가?
  9. NULL의 의미가 정의되어 있는가?
  10. 코드와 상태의 의미가 정의되어 있는가?

83장. 설계 단계별 체크리스트 — 물리 설계#

  1. 자료형 크기가 적절한가?
  2. 금액 정밀도가 충분한가?
  3. 대표 SQL이 정의되어 있는가?
  4. 인덱스가 실제 접근 패턴과 맞는가?
  5. 예상 데이터량을 계산했는가?
  6. 파티셔닝이 필요한가?
  7. 보존·삭제 전략이 있는가?
  8. PK·FK·UNIQUE·CHECK가 실제 DDL로 구현됐는가?
  9. 제품 고유 기능에 대한 의존성을 알고 있는가?
  10. 변경 DDL의 잠금과 실행 시간을 검증했는가?

84장. 구현·마이그레이션 체크리스트#

  1. 기존 데이터에 새 키를 어떻게 채울 것인가?
  2. 백필 순서는 무엇인가?
  3. 백필 중 서비스가 쓰기를 계속하는가?
  4. 구버전과 신버전이 동시에 동작하는가?
  5. 이중 쓰기 실패 가능성이 있는가?
  6. 기존 FK 참조를 모두 옮겼는가?
  7. 신규·기존 데이터를 모두 테스트했는가?
  8. 데이터 건수와 합계를 전후 비교했는가?
  9. 변경 후 고아 참조가 없는가?
  10. 기능 활성 시점이 따로 정의되어 있는가?

85장. 배포·롤백 체크리스트#

  1. Expand 단계가 정의되어 있는가?
  2. 백필 완료 기준은 무엇인가?
  3. 애플리케이션 전환 순서는 무엇인가?
  4. 이전 API는 언제 종료되는가?
  5. 새 업무 기능은 언제 활성화하는가?
  6. 어떤 시점까지 롤백 가능한가?
  7. 새 데이터 입력 뒤에도 롤백 가능한가?
  8. 롤백 시 데이터 의미가 손실되지 않는가?
  9. 관측 지표와 오류 알림이 있는가?
  10. 기존 구조 제거 기준이 명확한가?

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가 아닙니다.

실제 업무의 예외를 저장할 수 있고, 요구가 바뀌었을 때 무엇을 함께 바꿔야 하는지 설명할 수 있으며, 그 변경을 운영 데이터에 안전하게 전달할 수 있는 모델입니다.

이 페이지의 목차