데이터베이스를 만든다고 해서 처음부터 CREATE TABLE을 작성하는 것은 아닙니다.
먼저 어떤 데이터를 관리해야 하는지 정리하고, 그 데이터를 어떤 구조로 연결할지 설계한 다음, 마지막으로 PostgreSQL이나 MySQL 같은 실제 DBMS에서 사용할 수 있는 형태로 구현합니다.
이러한 데이터 모델링 과정은 일반적으로 다음 세 단계로 나눌 수 있습니다.
개념적 모델링
Conceptual Modeling
↓
논리적 모델링
Logical Modeling
↓
물리적 모델링
Physical Modeling
게임 데이터베이스를 만든다고 생각하면 이해하기 쉽습니다.
개념적
→ 게임에 플레이어, 길드, 아이템이 필요하겠네?
논리적
→ Player에 player_id가 필요하고
Guild와는 1:N 관계로 만들자.
물리적
→ player_id는 BIGINT,
nickname은 VARCHAR(30),
실제 PostgreSQL 테이블로 만들자.
즉 같은 데이터베이스를 설계하지만 단계가 내려갈수록 점점 더 구체적이고 실제 구현에 가까워집니다.
전체적인 흐름부터 보기
MMORPG 게임을 하나 만든다고 생각해봅시다.
게임에는 다음과 같은 기능이 필요합니다.
플레이어가 캐릭터를 생성할 수 있다.
플레이어는 길드에 가입할 수 있다.
플레이어는 여러 아이템을 소유할 수 있다.
아이템별 보유 수량을 저장해야 한다.
처음부터 다음 SQL을 작성하기는 어렵습니다.
CREATE TABLE player (
player_id BIGINT GENERATED ALWAYS AS IDENTITY,
nickname VARCHAR(30) NOT NULL,
level INTEGER NOT NULL DEFAULT 1,
guild_id BIGINT,
CONSTRAINT pk_player
PRIMARY KEY (player_id)
);
왜냐하면 그 전에 먼저 결정해야 할 것이 많기 때문입니다.
플레이어와 길드는 별도 데이터인가?
플레이어 한 명은 길드를 몇 개 가질 수 있는가?
아이템은 Player 안에 저장해야 하는가?
Player와 Item은 어떤 관계인가?
PK는 무엇으로 사용할 것인가?
그래서 큰 그림부터 시작해서 점점 구체화합니다.
개념적 모델링
**개념적 데이터 모델링(Conceptual Data Modeling)**은 가장 높은 수준의 설계 단계입니다.
이 단계에서는
우리 시스템에서 어떤 데이터를 관리해야 하는가?
를 결정합니다.
아직 PostgreSQL이나 MySQL 같은 DBMS는 생각하지 않습니다.
컬럼의 VARCHAR(30), INTEGER 같은 세부 데이터 타입도 중요하지 않습니다.
게임 요구사항을 먼저 살펴보자
게임 기획자가 다음 요구사항을 전달했다고 생각해봅시다.
플레이어는 닉네임과 레벨을 가진다.
플레이어는 하나의 길드에 가입할 수 있다.
하나의 길드에는 여러 플레이어가 가입할 수 있다.
플레이어는 여러 종류의 아이템을 보유할 수 있다.
같은 종류의 아이템은 여러 플레이어가 가질 수 있다.
이 요구사항을 보고 먼저 중요한 대상을 찾습니다.
Player
Guild
Item
이것들이 Entity 후보가 됩니다.
개념적 모델에서 중요한 것은 Entity와 관계
개념적 모델에서는 세부적인 컬럼보다는 무엇이 존재하고 서로 어떻게 연결되는가가 중요합니다.
게임의 개념적 모델을 단순하게 나타내면 다음과 같습니다.
[Guild]
│
│ 1 : N
│
[Player]
│
│ N : M
│
[Item]
여기서 알 수 있는 것은 다음 정도입니다.
Guild
→ 여러 Player를 가질 수 있다.
Player
→ 하나의 Guild에 가입한다.
Player
→ 여러 Item을 가질 수 있다.
Item
→ 여러 Player가 소유할 수 있다.
아직
guild_id BIGINT
nickname VARCHAR(30)
price NUMERIC(10, 2)
같은 내용은 필요하지 않습니다.
개념적 모델의 실제 데이터 예시
현재 기획 단계에서 게임 데이터를 대충 다음처럼 생각할 수 있습니다.
플레이어
플레이어 | 레벨 |
|---|---|
천기사 | 35 |
흑마법사 | 42 |
궁수왕 | 28 |
길드
길드 |
|---|
용기사단 |
마법협회 |
아이템
아이템 |
|---|
철검 |
포션 |
지팡이 |
그리고 관계를 생각합니다.
플레이어 | 가입 길드 |
|---|---|
천기사 | 용기사단 |
흑마법사 | 마법협회 |
궁수왕 | 용기사단 |
이 단계에서 중요한 것은
플레이어와 길드라는 별도의 개념이 존재하고 둘 사이에 관계가 있다.
는 사실입니다.
개념적 모델링에서 결정하는 것
대표적으로 다음을 결정합니다.
항목 | 게임 예시 |
|---|---|
핵심 Entity | Player, Guild, Item |
Entity의 의미 | 플레이어, 길드, 아이템 |
주요 관계 | Player 가입 Guild |
관계의 형태 | Guild 1:N Player |
주요 업무 규칙 | 한 Player는 최대 하나의 Guild 가입 |
즉 업무와 현실 세계의 구조를 데이터 관점으로 정리하는 단계입니다.
논리적 모델링
개념적 모델에서
Player
Guild
Item
이라는 주요 Entity와 관계를 찾았습니다.
다음은 **논리적 데이터 모델링(Logical Data Modeling)**입니다.
논리적 모델에서는
이 데이터를 관계형 데이터베이스 구조로 어떻게 표현할 것인가?
를 구체적으로 결정합니다.
이때부터 PK, FK, Attribute, 정규화 같은 개념이 본격적으로 등장합니다.
Player를 구체적으로 만들어보자
개념적 모델에서는 단순히
Player
라고만 했다면 논리적 모델에서는 어떤 속성이 필요한지 결정합니다.
그리고 각 컬럼의 역할도 결정합니다.
player_id → PK
guild_id → FK
따라서
형태가 됩니다.
Guild도 구체화한다
Guild 역시 속성을 결정합니다.
이제 Player의
guild_id FK
가
Guild.guild_id PK
를 참조합니다.
Guild.guild_id
▲
│
│ 참조
│
Player.guild_id
논리적인 관계가 완성됩니다.
N:M 관계도 해결해야 한다
개념적 모델에서는 다음 관계가 있었습니다.
Player N : M Item
게임을 생각하면 자연스러운 관계입니다.
천기사가 여러 아이템을 가지고 있고
포션 역시 여러 플레이어가 가지고 있습니다.
하지만 관계형 데이터베이스에서는 보통 이 N:M 관계를 그대로 두지 않고 중간 Entity를 생성합니다.
게임에서는 Inventory가 적절합니다.
Inventory 추가
논리적 모델은 다음과 같이 변경됩니다.
관계는
Player 1 : N Inventory
Item 1 : N Inventory
가 됩니다.
원래
Player N : M Item
이었던 관계를 두 개의 1:N으로 바꾼 것입니다.
실제 데이터를 논리 구조에 넣어보자
Player
player_id | nickname | level | guild_id |
|---|---|---|---|
1 | 천기사 | 35 | 10 |
2 | 흑마법사 | 42 | 20 |
3 | 궁수왕 | 28 | 10 |
Guild
guild_id | guild_name |
|---|---|
10 | 용기사단 |
20 | 마법협회 |
Item
item_id | item_name | price |
|---|---|---|
101 | 철검 | 1500 |
102 | 포션 | 100 |
103 | 지팡이 | 2500 |
Inventory
player_id | item_id | quantity |
|---|---|---|
1 | 101 | 1 |
1 | 102 | 5 |
2 | 102 | 10 |
2 | 103 | 1 |
3 | 102 | 3 |
이 정도가 되면 실제 관계형 데이터베이스의 구조와 상당히 비슷해졌습니다.
논리적 모델링에서는 정규화도 진행한다
논리적 모델을 설계하면서 데이터가 적절하게 분리되어 있는지 확인합니다.
예를 들어 처음에 다음 테이블을 만들었다고 해봅시다.
player_id | nickname | guild_id | guild_name |
|---|---|---|---|
1 | 천기사 | 10 | 용기사단 |
2 | 흑마법사 | 20 | 마법협회 |
3 | 궁수왕 | 10 | 용기사단 |
여기에는
guild_id → guild_name
이라는 종속 관계가 존재합니다.
guild_name을 Player에 계속 저장할 필요가 없습니다.
그래서
Player
player_id
nickname
guild_id
와
Guild
guild_id
guild_name
로 분리합니다.
즉 우리가 앞에서 공부한 1NF, 2NF, 3NF 등의 정규화 과정은 주로 논리적 모델링 단계에서 수행됩니다.
논리적 모델링에서 결정하는 것
항목 | 게임 예시 |
|---|---|
Entity | Player, Guild, Item, Inventory |
Attribute | nickname, level, price |
PK | player_id, guild_id |
FK | Player.guild_id |
관계 | Guild 1:N Player |
N:M 해소 | Inventory 생성 |
정규화 | Player와 Guild 정보 분리 |
무결성 규칙 | Inventory는 Player와 Item을 참조 |
개념적 모델보다 훨씬 구체적입니다.
하지만 아직도 특정 DBMS에서 실제로 어떻게 저장할지는 결정하지 않았습니다.
물리적 모델링
마지막은 **물리적 데이터 모델링(Physical Data Modeling)**입니다.
물리적 모델은
논리적으로 설계한 구조를 실제 DBMS에서 어떻게 구현할 것인가?
를 결정하는 단계입니다.
여기부터는 PostgreSQL, MySQL, Oracle 같은 실제 DBMS의 특성이 중요해집니다.
논리적 모델과 물리적 모델의 차이
논리적 모델에서 다음과 같이 설계했다고 해봅시다.
물리적 모델에서는 훨씬 구체적으로 결정해야 합니다.
player_id
→ BIGINT
→ PRIMARY KEY
→ 자동 증가
nickname
→ VARCHAR(30)
→ NOT NULL
→ UNIQUE
level
→ SMALLINT
→ NOT NULL
→ DEFAULT 1
guild_id
→ BIGINT
→ NULL 허용
→ FOREIGN KEY
즉 실제 저장 방법을 결정합니다.
Player의 물리적 모델 예시
PostgreSQL을 사용한다고 가정해보겠습니다.
실제 SQL로 만들면 다음과 비슷합니다.
CREATE TABLE player (
player_id BIGINT GENERATED ALWAYS AS IDENTITY,
nickname VARCHAR(30) NOT NULL,
level SMALLINT NOT NULL DEFAULT 1,
guild_id BIGINT,
CONSTRAINT pk_player
PRIMARY KEY (player_id),
CONSTRAINT uq_player_nickname
UNIQUE (nickname),
CONSTRAINT ck_player_level
CHECK (level >= 1),
CONSTRAINT fk_player_guild
FOREIGN KEY (guild_id)
REFERENCES guild(guild_id)
);
이제 단순한 데이터 모델이 아니라 실제로 DBMS에서 실행 가능한 구조가 되었습니다.
데이터 타입을 결정한다
물리적 모델링에서는 컬럼마다 실제 데이터 타입을 결정합니다.
예를 들어 다음처럼 설계할 수 있습니다.
컬럼 | 의미 | PostgreSQL 타입 |
|---|---|---|
player_id | 플레이어 번호 | BIGINT |
nickname | 닉네임 | VARCHAR(30) |
level | 레벨 | SMALLINT |
gold | 골드 | BIGINT |
created_at | 생성일 | TIMESTAMP |
is_active | 활성 상태 | BOOLEAN |
논리적 모델에서는 단순히
레벨
골드
가입일
정도로 생각했다면,
물리적 모델에서는 DB가 실제로 어떻게 저장할지까지 결정합니다.
제약조건도 구체적으로 결정한다
게임의 Player 데이터에 다음 규칙이 있다고 해봅시다.
닉네임은 반드시 존재해야 한다.
닉네임은 중복되면 안 된다.
레벨은 1 이상이어야 한다.
길드 가입은 선택 사항이다.
이를 물리적 모델에서는 다음처럼 표현할 수 있습니다.
규칙 | DB 제약조건 |
|---|---|
닉네임 필수 | NOT NULL |
닉네임 중복 금지 | UNIQUE |
레벨 1 이상 | CHECK |
길드 선택 | guild_id NULL 허용 |
Player 식별 | PRIMARY KEY |
Guild 참조 | FOREIGN KEY |
즉 물리적 모델링에서는 단순히 어떤 데이터가 필요한가가 아니라,
잘못된 데이터가 들어오지 않도록 DB 자체에서 어떻게 제한할 것인가?
까지 생각합니다.
인덱스도 물리적 모델링에 포함된다
플레이어가 100명일 때는 대부분의 검색이 빠릅니다.
하지만 플레이어가 1,000만 명이라면 이야기가 달라집니다.
예를 들어 다음 검색이 자주 실행된다고 해봅시다.
SELECT *
FROM player
WHERE nickname = '천기사';
닉네임 검색이 매우 자주 발생한다면 nickname에 인덱스를 추가할 수 있습니다.
CREATE INDEX idx_player_nickname
ON player(nickname);
또 길드별 플레이어 조회가 자주 발생한다면
CREATE INDEX idx_player_guild_id
ON player(guild_id);
를 고려할 수 있습니다.
이러한 성능을 위한 인덱스 설계는 대표적인 물리적 모델링 작업입니다.
논리적으로 같은 모델도 물리적으로 달라질 수 있다
논리 모델이 다음과 같다고 해봅시다.
Player
player_id
nickname
created_at
PostgreSQL에서는
player_id BIGINT GENERATED ALWAYS AS IDENTITY
created_at TIMESTAMP
처럼 구현할 수 있습니다.
다른 DBMS에서는 자동 증가 컬럼이나 날짜 타입의 구체적인 문법이 달라질 수 있습니다.
즉
논리적 구조
→ DBMS와 크게 독립적
물리적 구조
→ 사용하는 DBMS에 영향을 받음
이라고 볼 수 있습니다.
같은 게임 DB가 단계별로 어떻게 변하는가?
하나의 예제를 처음부터 끝까지 연결해보겠습니다.
1단계 — 요구사항
플레이어가 존재한다.
플레이어는 길드에 가입한다.
플레이어는 여러 아이템을 가진다.
2단계 — 개념적 모델
Guild
Player
Item
관계는
Guild 1 : N Player
Player N : M Item
정도로 표현합니다.
3단계 — 논리적 모델
N:M 관계를 Inventory로 분리하고 PK와 FK까지 결정했습니다.
4단계 — 물리적 모델
그리고 필요하다면
INDEX
CHECK
UNIQUE
DEFAULT
NOT NULL
FK 동작
까지 추가합니다.
세 단계를 표로 비교
구분 | 개념적 모델 | 논리적 모델 | 물리적 모델 |
|---|---|---|---|
핵심 질문 | 무엇을 관리할까? | 어떻게 구조화할까? | 실제 DB에 어떻게 만들까? |
관점 | 업무·사용자 | 데이터 구조 | DBMS 구현 |
Entity | O | O | Table로 구현 |
Attribute | 주요 속성 중심 | 구체적으로 정의 | 실제 Column |
관계 | O | O | FK로 구현 |
PK/FK | 거의 생략 가능 | 결정 | 실제 제약조건 |
정규화 | 거의 하지 않음 | 중요 | 결과 반영 |
데이터 타입 | X | 추상적으로 표현 가능 | VARCHAR, INTEGER 등 |
인덱스 | X | X | O |
DBMS 의존 | 거의 없음 | 낮음 | 높음 |
SQL | X | X | 실제 작성 |
가장 쉽게 기억하면 다음과 같습니다.
개념적
= 무엇?
논리적
= 어떻게 연결?
물리적
= 실제로 어떻게 저장?
게임 건물 설계로 비유하면
데이터 모델링은 게임의 건물을 설계하는 과정과 비슷하게 생각할 수도 있습니다.
개념적
마을에 무엇이 필요하지?
→ 상점
→ 대장간
→ 여관
아직 건물의 정확한 크기나 재료까지는 정하지 않습니다.
논리적
상점에는 판매 공간과 창고가 필요하다.
대장간은 상점 옆에 위치한다.
여관에는 여러 개의 방이 있다.
구조와 관계가 구체적으로 만들어집니다.
물리적
상점 크기 = 20m × 15m
벽 재료 = 돌
문 = 2개
창고 크기 = 5m × 5m
실제로 건설할 수 있을 정도로 구체적인 설계가 됩니다.
데이터베이스도 마찬가지입니다.
개념적
→ 시스템의 큰 구조
논리적
→ 데이터의 구조와 관계
물리적
→ 실제 DBMS 구현
ERD는 어느 단계에서 만드는가?
ERD는 특정 한 단계에서만 사용하는 것은 아닙니다.
모델링 수준에 따라 ERD에 들어가는 정보가 달라집니다.
개념 ERD
[Guild]
│
│ 1:N
│
[Player]
│
│ N:M
│
[Item]
주요 Entity와 관계 중심입니다.
논리 ERD
PK, FK와 속성이 구체적으로 들어갑니다.
물리 ERD
실제 컬럼명과 데이터 타입까지 표현할 수 있습니다.
즉 같은 ERD라도 어느 수준까지 표현하느냐에 따라 개념 ERD, 논리 ERD, 물리 ERD로 나눌 수 있습니다.
실무에서는 한 번에 끝나지 않는다
모델링 과정이 항상
개념 → 논리 → 물리 → 끝
처럼 한 방향으로만 진행되는 것은 아닙니다.
예를 들어 물리적 모델을 만드는 도중 다음 문제가 발견될 수 있습니다.
Player 테이블이 너무 크다.
특정 조회가 너무 느리다.
현재 구조로는 새로운 게임 기능을 표현하기 어렵다.
그러면 다시 논리적 모델을 수정할 수 있습니다.
개념
↓
논리
↓
물리
↑
│
구조 수정
즉 모델링은 실제 요구사항과 성능을 확인하면서 계속 보완할 수 있습니다.
정규화와 ERD는 어디에 들어가는가?
지금까지 공부한 내용들을 연결해보면 전체 흐름이 더 명확해집니다.
게임 요구사항 분석
↓
개념적 모델링
Entity와 관계 찾기
↓
논리적 모델링
Attribute
PK / FK
관계
정규화
↓
ERD 구체화
IE 표기법으로 관계 표현
↓
물리적 모델링
데이터 타입
제약조건
인덱스
DBMS 선택
↓
CREATE TABLE
↓
실제 데이터베이스
즉 우리가 공부했던 관계형 데이터 모델링, 정규화, ERD, IE 표기법, PK/FK는 따로 떨어져 있는 개념이 아니라 하나의 데이터베이스 설계 과정 안에서 연결됩니다.
세 단계에서 같은 Player를 비교해보기
마지막으로 같은 Player 데이터를 단계별로 비교해보겠습니다.
개념적 모델
Player
플레이어를 관리해야 한다.
논리적 모델
속성 | 역할 |
|---|---|
player_id | PK |
nickname | 플레이어 닉네임 |
level | 레벨 |
guild_id | Guild FK |
물리적 모델
컬럼 | 타입 | 제약조건 |
|---|---|---|
player_id | BIGINT | PK, IDENTITY |
nickname | VARCHAR(30) | NOT NULL, UNIQUE |
level | SMALLINT | NOT NULL, DEFAULT 1 |
guild_id | BIGINT | FK, NULL 허용 |
CREATE TABLE player (
player_id BIGINT GENERATED ALWAYS AS IDENTITY,
nickname VARCHAR(30) NOT NULL,
level SMALLINT NOT NULL DEFAULT 1,
guild_id BIGINT,
PRIMARY KEY (player_id),
UNIQUE (nickname),
FOREIGN KEY (guild_id)
REFERENCES guild(guild_id)
);
처음에는 단순히
Player가 필요하다.
였던 생각이 마지막에는 실제 DBMS가 실행할 수 있는 SQL로 변한 것입니다.
정리
데이터 모델링은 일반적으로 개념적 → 논리적 → 물리적 모델링의 세 단계로 구체화됩니다.
단계 | 한 문장으로 |
|---|---|
개념적 모델링 | 어떤 데이터가 필요한지 찾는다. |
논리적 모델링 | 데이터를 어떤 구조와 관계로 저장할지 설계한다. |
물리적 모델링 | 실제 DBMS에서 사용할 테이블과 컬럼으로 구현한다. |
게임 데이터베이스를 예로 들면 다음과 같습니다.
개념적
플레이어
길드
아이템이 필요하다.
↓
논리적
Player
Guild
Item
Inventory
PK / FK 결정
1:N 관계 결정
정규화 진행
↓
물리적
player_id BIGINT
nickname VARCHAR(30)
level SMALLINT
PRIMARY KEY
FOREIGN KEY
UNIQUE
INDEX
따라서 가장 간단하게 기억하면 다음과 같습니다.
개념적 모델링은 "무엇을 저장할까?" 논리적 모델링은 "어떻게 구조화할까?" 물리적 모델링은 "실제 DB에 어떻게 만들까?"
이 세 단계가 끝나면 비로소 설계된 ERD를 기반으로 CREATE TABLE을 작성하고 실제 데이터베이스를 구축할 수 있습니다.
댓글 0
댓글을 불러오는 중…