---
term: '데이터 모델링 (Pointer와 Relation)'
seoTitle: '데이터 모델링이란? 관계 유형, Pointer와 Relation'
headline: '데이터 모델링이란?'
slug: data-modeling
category: database
shortDefinition: '데이터 모델링은 데이터베이스가 데이터를 어떻게 저장할지 정하기 전에 엔터티, 속성, 관계를 정리하는 과정입니다.'
relatedTerms:
  - database-schema
  - relational-queries-document-databases
  - n-plus-one-query-problem
  - auto-generated-database-apis
contrastsWith:
  - database-schema
faq:
  - question: '데이터 모델링이란 무엇입니까?'
    answer: '애플리케이션의 정보, 즉 엔터티와 그 속성, 엔터티 사이의 관계를 데이터베이스가 저장할 수 있는 설계로 옮기는 과정입니다. 보통 세 단계의 상세 수준을 거칩니다. 엔터티의 이름을 정하는 개념 모델, 속성과 키를 더하는 논리 모델, 특정 엔진의 테이블, 열, 인덱스로 확정하는 물리 모델입니다.'
  - question: '데이터 모델의 세 가지 유형은 무엇입니까?'
    answer: '개념, 논리, 물리 모델로, 같은 설계를 점점 확대해서 보는 것입니다. 개념 모델은 비즈니스 이해관계자를 위해 무엇이 존재하고 서로 어떻게 연결되는지에 답합니다. 논리 모델은 특정 기술에 얽매이지 않은 채 속성, 타입, 키를 더합니다. 물리 모델은 실제 엔진에 맞춰 테이블이나 컬렉션, 인덱스, 제약 조건을 확정합니다. 각 수준은 이전 수준에 결정을 더한 것입니다.'
  - question: '일대다 관계란 무엇입니까?'
    answer: '하나의 부모 레코드가 여러 자식과 연결되고, 각 자식은 정확히 하나의 부모에 속하는 관계입니다. 고객과 주문, 저자와 책이 그 예입니다. 어떤 스키마에서든 가장 흔한 관계입니다. 관계형 데이터베이스는 "다" 쪽에 외래 키를 두어 구현하고, 문서 데이터베이스는 작고 크기가 제한된 집합에는 임베딩된 배열을, 그 밖의 모든 경우에는 부모를 가리키는 포인터를 사용합니다.'
  - question: '다대다 관계란 무엇이며, 왜 연결 테이블이 필요합니까?'
    answer: '양쪽 모두가 상대편의 여러 레코드와 연결되는 관계입니다. 학생과 강좌, 책과 장르가 그 예입니다. 외래 키 열 하나는 한 행만 가리킬 수 있으므로, 관계형 데이터베이스는 다대다를 두 개의 일대다 관계로 분해하고, 두 키를 모두 담는 연결 테이블(종종 등록일 같은 관계 속성도 함께 담습니다)로 이어 줍니다. 문서 데이터베이스는 연결 테이블을 생략합니다. 참조 배열이나 전용 관계 타입이 연결을 직접 담습니다.'
  - question: '일대다와 다대다는 어떻게 구분합니까?'
    answer: '소유 관계를 양방향으로 질문하면 됩니다. "한 저자가 여러 책을 가질 수 있습니까?" 그렇습니다. "한 책이 여러 저자를 가질 수 있습니까?" 아니라면 일대다이며 외래 키는 책 쪽에 둡니다. 그렇다면 다대다이며 연결 테이블이나 관계 타입을 사용합니다. 이를 잘못 판단하는 것이 대표적인 모델링 실수입니다. 일대다로 모델링한 다대다는 행을 중복시키거나 연결을 조용히 잃어버립니다.'
  - question: 'ER 다이어그램에서 카디널리티란 무엇입니까?'
    answer: '한 엔터티의 인스턴스가 다른 엔터티의 인스턴스와 몇 개까지 연결될 수 있는지를 나타냅니다. 일대일, 일대다, 다대다가 있습니다. 다이어그램은 세 가지 표기법 중 하나로 이를 표현합니다. 연결선 위의 숫자와 문자, 까마귀발(crow''s foot) 기호(하나는 막대, 다수는 세 갈래 포크), 또는 1과 별표 같은 다중성 범위입니다. 의미는 같고 그리는 관례만 다릅니다.'
  - question: '문서 데이터베이스는 외래 키 없이 관계를 어떻게 모델링합니까?'
    answer: '세 가지 도구를 사용합니다. 임베딩(관련 데이터를 부모 문서 안에 중첩하는 방식으로, 함께 읽히고 부모가 소유하며 크기가 제한될 때 적합합니다), 포인터 또는 참조(관련 문서의 ID를 타입이 지정된 필드로 저장하는 방식으로, 생명 주기가 독립적이고 카디널리티가 높을 때 적합합니다), 그리고 다대다를 위한 관계 객체나 ID 배열입니다. 지침이 되는 규칙도 정규화에서 접근 패턴으로 바뀝니다. 함께 읽히는 데이터는 함께 저장해야 합니다.'
  - question: '가장 흔한 데이터 모델링 실수는 무엇입니까?'
    answer: '늘 같은 다섯 가지입니다. 다대다를 일대다로 모델링하는 것, 외래 키와 포인터 필드에 인덱스를 빠뜨리는 것(모든 조인과 조회가 그 대가를 치릅니다), 문서 스키마에 크기 제한이 없는 임베딩 배열을 두는 것, 느린 쿼리가 확인되기도 전에 섣불리 비정규화하는 것, 그리고 모델이 처리해야 할 쿼리, 즉 접근 패턴을 나열하지 않고 엔터티만으로 모델링하는 것입니다.'
codeLanguages: [javascript, dart, swift, kotlin]
externalAuthorities:
  - name: 'MongoDB — Embedding vs. References'
    url: 'https://www.mongodb.com/docs/manual/data-modeling/concepts/embedding-vs-references/'
  - name: 'Many-to-many data model (Wikipedia)'
    url: 'https://en.wikipedia.org/wiki/Many-to-many_(data_model)'
  - name: 'Entity–relationship model (Wikipedia)'
    url: 'https://en.wikipedia.org/wiki/Entity%E2%80%93relationship_model'
  - name: 'SDK relational data guide'
    url: 'https://docs.parseplatform.org/js/guide/#relational-data'
cta:
  title: '한 번 모델링하고, 어디서나 쿼리하기'
  text: 'Back4app에서는 모델이 곧 백엔드입니다. 대시보드의 클래스, 일대다를 위한 Pointer, 다대다를 위한 Relation으로 모델을 만들고, 선언한 모든 연결을 include()가 내장된 자동 생성 REST, GraphQL, SDK로 즉시 쿼리할 수 있습니다.'
  linkText: '무료로 시작하기'
  linkUrl: 'https://www.back4app.com/signup'
author: 'Back4app Engineering'
publishedDate: '2026-10-30'
translationKey: data-modeling
---

**데이터 모델링은 데이터베이스가 데이터를 어떻게 저장할지 정하기 전에 엔터티, 속성, 관계를 정리하는 과정입니다.** 엔터티는 쉬운 부분입니다. 어떤 제품이든 자신의 사용자, 주문, 게시물이 무엇인지 압니다. 진짜 기술은 *관계*에 있습니다. 어떤 레코드가 어떤 레코드를 가리키는지, 몇 개나 가리키는지, 그 연결이 물리적으로 어디에 존재하는지입니다. 연결을 제대로 잡으면 쿼리는 저절로 써지고, 잘못 잡으면 모든 기능이 스키마와 싸우게 됩니다.

## 핵심 요약

| 질문 | 답변 |
| --- | --- |
| 세 가지 수준 | 개념(무엇) → 논리(구조) → 물리(엔진) |
| 세 가지 관계 유형 | 일대일 · 일대다 · 다대다 |
| 관계형 도구 | 외래 키, 다대다에는 연결 테이블 |
| 문서형 도구 | 임베딩, Pointer(참조), Relation/ID 배열 |
| 현대의 규칙 | 접근 패턴에 맞춰 모델링 – 함께 읽는 데이터는 함께 저장 |

## 같은 관계, 두 세계

먼저 관계형입니다. 외래 키가 일대다를 담당하고, 다대다는 연결 테이블을 통해 분해해야 합니다:

```sql
-- 1:N — 외래 키는 "다" 쪽에 둡니다
CREATE TABLE books (
  id        bigint PRIMARY KEY,
  title     text   NOT NULL,
  author_id bigint NOT NULL REFERENCES authors(id) ON DELETE CASCADE
);

-- M:N — FK 하나로는 표현할 수 없으므로 연결 테이블이 두 키를 모두 담습니다
CREATE TABLE book_genres (
  book_id  bigint REFERENCES books(id),
  genre_id bigint REFERENCES genres(id),
  added_at timestamptz DEFAULT now(),     -- 연결 테이블도 속성을 가질 수 있음
  PRIMARY KEY (book_id, genre_id)         -- 복합 키: 하나의 연결은 한 번만
);
```

문서 세계에서도 모델은 같습니다. 일대다 연결은 타입이 지정된 **Pointer**이고, 다대다 연결은 **Relation**입니다. 따로 고안할 연결 테이블이 없으며, 연결은 애플리케이션 코드에서 선언합니다:

**JavaScript:**

```javascript
// JavaScript / Node.js — Back4app JS SDK
// One-to-many: a Pointer. Many-to-many: a Relation.
const author = await new Parse.Query('Author').get(authorId);

const book = new Parse.Object('Book');
book.set('title', 'Dune');
book.set('author', author);              // Pointer — typed one-to-many edge
await book.save();

const genres = book.relation('genres');  // Relation — many-to-many, unbounded
genres.add([sciFi, classics]);
await book.save();
```

**Flutter:**

```dart
// Flutter / Dart — Back4app Flutter SDK
// One-to-many: a Pointer. Many-to-many: a Relation.
final book = ParseObject('Book')
  ..set('title', 'Dune')
  ..set('author', author.toPointer()); // Pointer — typed one-to-many edge
await book.save();

book.addRelation('genres', [sciFi, classics]); // Relation — many-to-many
await book.save();
```

**Swift:**

```swift
// iOS / Swift — Back4app Swift SDK
// One-to-many: a Pointer. Many-to-many: a Relation.
var book = Book()
book.title = "Dune"
book.author = try author.toPointer()   // Pointer — typed one-to-many edge
let saved = try await book.save()

let relation = try saved.relation("genres")
try await relation.add([sciFi, classics]).save() // Relation — many-to-many
```

**Kotlin:**

```kotlin
// Android / Kotlin — Back4app Android SDK
// One-to-many: a Pointer. Many-to-many: a Relation.
val book = ParseObject("Book").apply {
  put("title", "Dune")
  put("author", author)                 // Pointer — typed one-to-many edge
}
book.save()

val genres = book.getRelation<ParseObject>("genres") // Relation — many-to-many
genres.add(sciFi)
genres.add(classics)
book.save()
```

## 개념 vs. 논리 vs. 물리: 세 가지 수준

```mermaid
flowchart LR
  accTitle: 개념, 논리, 물리 데이터 모델
  accDescr: 개념 모델은 비즈니스 논의를 위해 엔터티와 관계의 이름을 정하고, 논리 모델은 기술과 무관하게 속성, 타입, 키를 더하며, 물리 모델은 특정 데이터베이스 엔진의 테이블이나 컬렉션, 인덱스, 제약 조건으로 확정합니다.
  C["개념<br/>저자 —집필→ 책<br/>(존재하는 것, 사람을 위한 모델)"]
  L["논리<br/>+ 속성, 타입, 키<br/>(구조, 엔진 독립적)"]
  P["물리<br/>한 엔진의 테이블/컬렉션,<br/>인덱스, 제약 조건"]
  C --> L --> P
```

| 구분 | 개념 | 논리 | 물리 |
| --- | --- | --- | --- |
| 답하는 질문 | 무엇이 존재하고 어떻게 연결되는지 | 어떤 필드, 어떤 키 | 어떻게 저장되고 얼마나 빠른지 |
| 대상 | 모든 사람 | 설계자 | 엔지니어 + 엔진 |
| 담는 내용 | 엔터티, 관계 | + 속성, 타입, 카디널리티 | + 인덱스, 제약 조건, 파티션 |
| 바뀌는 시점 | 비즈니스가 바뀔 때 | 요구 사항이 구체화될 때 | 엔진이나 규모가 바뀔 때 |

세 수준이 강제하는 규율은 단순합니다. 팀이 아직 "주문"이 무엇인지 합의하지 못했다면, 인덱스를 두고 논쟁할 때가 아닙니다.

## 모든 관계, 모든 구현

검색 결과 상위 문서들이 만들지 않은 표입니다. 각 연결 유형과 두 세계에서의 구현을 함께 정리했습니다:

| 관계 | 예시 | 관계형 구현 | 문서형 구현 |
| --- | --- | --- | --- |
| 일대일 | 사용자 ↔ 프로필 | UNIQUE가 붙은 FK, 또는 같은 행 | 임베딩 – 거의 항상 |
| 일대소수 | 사람 → 주소 | 자식 테이블 + FK | 임베딩된 배열(크기 제한) |
| 일대다 | 저자 → 책 | "다" 쪽의 FK | "다" 쪽의 **Pointer** |
| 일대초대량 | 디바이스 → 이벤트 | FK + 파티셔닝 | *자식* 쪽의 Pointer, 배열은 절대 사용하지 않음 |
| 다대다 | 책 ↔ 장르 | 연결 테이블, 복합 PK | **Relation** 또는 Pointer 배열 |
| 자기 참조 | 직원 → 상사 | 자기 테이블을 가리키는 FK | 자기 클래스를 가리키는 Pointer |

다이어그램을 읽기 위한 카디널리티 표기법도 알아 둘 만합니다. 까마귀발(crow's foot) 표기법은 "하나"를 막대로, "다수"를 세 갈래 포크로 그립니다. [ER 전통](https://en.wikipedia.org/wiki/Entity%E2%80%93relationship_model)은 선 위에 1과 N을 적고, UML은 `1`과 `*` 같은 다중성을 적습니다. 방언은 셋이지만 문법은 하나입니다.

## 정규화, 비정규화, 아니면 임베딩?

관계형 모델링은 **정규화**에서 출발합니다. 모든 사실이 한 곳에만 존재하도록 데이터를 나누어, 쓰기를 이상 현상(anomaly)으로부터 보호합니다. 분석 모델링과 문서 모델링은 이 원칙을 의도적으로 구부립니다. **비정규화**는 읽기가 잦은 필드를 복제해 조인을 없애고, **임베딩**은 비정규화의 문서 네이티브 사촌입니다. [임베딩 vs. 참조 체크리스트](https://www.mongodb.com/docs/manual/data-modeling/concepts/embedding-vs-references/)는 세 가지 질문으로 압축됩니다. 함께 읽힙니까? 하나의 부모가 소유합니까? 크기가 제한됩니까? 셋 다 "예"라면 임베딩하고, 하나라도 "아니요"라면 참조합니다. 그리고 이 모든 것 위에, 범용 가이드들이 건너뛰는 현대의 규칙이 있습니다. **쿼리부터 나열하는 것**입니다. 모델이 처리해야 할 접근 패턴을 적은 비용으로 처리할 수 있을 때 그 모델은 올바르며, 엔터티만으로는 그것을 알 수 없습니다.

## 일반적인 사용 사례

- **새 백엔드 설계.** 고전적인 절차입니다. 엔터티의 이름을 정하고, 연결을 그리고, 위 표에 따라 연결마다 구현을 고릅니다. 이 모든 일을 첫 줄의 코드를 쓰기 전에 합니다.
- **속성을 가진 연결 테이블 패턴.** 수강 등록, 멤버십, 주문 항목처럼 *연결 자체*에 데이터(날짜, 수량, 역할)가 있다면, 연결 테이블(또는 Pointer 두 개를 가진 연결 클래스)은 일급 엔터티입니다.
- **커져 버린 스키마 정리.** 증상이 연결을 가리킵니다. 중복된 행은 잘못 모델링된 다대다를, 비대해진 문서는 크기 제한 없는 임베딩을 드러냅니다.
- **세계 간 마이그레이션.** 관계형에서 문서형으로 옮긴다는 것은 모든 FK를 임베딩으로 할지 Pointer로 할지 다시 결정한다는 뜻이며, 위의 표가 그 번역 사전입니다.
- **AI와 분석 피드.** 데이터 웨어하우스는 연결이 명시적이고 안정적이기를 원합니다. 모델링 부채는 데이터를 내보내려는 날 드러납니다.

## 각 연결은 어떻게 모델링해야 할까? 결정 매트릭스

| 선택… | 이런 경우… | 주의할 점… |
| --- | --- | --- |
| 임베딩 | 함께 읽힘, 부모가 소유, 크기 제한 | 증가: 오늘의 "소수"가 내일은 수천 개가 됨 |
| Pointer / FK | 독립적인 생명 주기, 높은 카디널리티 | 인덱스 필수 – 모든 조회와 include가 대가를 치름 |
| Relation / 연결 테이블 | 다대다, 또는 연결 자체가 데이터를 가짐 | 쿼리 방향: 어느 쪽에서 질의할지 미리 파악 |
| 복제(비정규화) | 연결을 가로질러 자주 읽히는 필드 | 업데이트 팬아웃 – 복제는 이자가 붙는 부채 |

## 한계와 트레이드오프

- **모델은 가정을 고정합니다.** "사용자는 주소가 하나" 같은 카디널리티 결정은 스키마가 됩니다. 종이 위에서는 바꾸기 쉽지만 출시 후에는 비쌉니다. 내일의 카디널리티를 기준으로 모델링해야 합니다.
- **두 세계 모두 인덱스 없는 연결에 대가를 치르게 합니다.** FK 열과 Pointer 필드는 조인 경로입니다. 여기에 인덱스를 빠뜨리는 것이 가장 흔한, 그리고 드러나지 않는 성능 버그입니다.
- **임베딩은 무결성을 지역성과 맞바꿉니다.** 임베딩된 사본이 원본과 일관성을 유지하도록 강제하는 엔진은 없습니다. 그 책임은 이제 애플리케이션의 업데이트 로직에 있습니다.
- **연결 테이블은 조인을 늘리고, Relation은 조인을 숨깁니다.** 모든 다대다 읽기는 연결 저장소를 거칩니다. 어느 세계에 있든 쿼리 경로의 비용을 미리 계산해야 합니다.
- **접근 패턴 중심 모델링에도 비용이 있습니다.** 오늘의 쿼리에 최적화하면 스키마가 오늘의 제품에 묶일 수 있습니다. 중립적인 기준점으로 개념 모델을 계속 유지하는 것이 좋습니다.

## Back4app에서의 데이터 모델링

Back4app은 관리형 데이터베이스, 자동 생성되는 REST 및 GraphQL API, 인증, 파일 스토리지, Cloud Code 서버리스 함수를 결합한 오픈소스 Backend as a Service(BaaS) 플랫폼입니다. Back4app의 모델링 어휘는 위 표들의 문서형 열을 일급 기능으로 만든 것입니다. [**Pointer**](https://docs.parseplatform.org/js/guide/#relational-data)는 타입이 지정된 일대다 연결이고, **Relation**은 직접 만든 연결 테이블 없이 다대다를 담당하며, 배열은 크기가 제한된 소수 관계를 처리합니다. 선언한 모든 연결은 즉시 탐색할 수 있습니다. `include()`는 한 번의 요청으로 Pointer를 따라가고, GraphQL은 한 번의 쿼리로 Relation을 중첩해 가져옵니다. 스키마는 대시보드에서 늘 보이고 편집할 수 있으므로, 물리 모델이 개념 모델을 설계한 팀의 시야에서 벗어나는 일이 없습니다.
