카테고리 없음

[유레카 / 백엔드] TIL - 24 (MongoDB vs RDBMS)

coding-quokka101 2026. 3. 5. 11:08

📌 들어가며

오늘의 학습 목표: 관계형 DB의 ERD 설계와 MongoDB의 Document 설계를 비교하며 이해하고, 상황에 맞는 데이터 모델링 선택하기

안녕하세요! 오늘은 데이터베이스 설계에 대해 공부했습니다.

사실 이번 주제를 공부하게 된 계기가 있어요. 프로젝트에서 "게시글 + 댓글" 기능을 설계하는데, 팀원이 이런 질문을 했거든요.

"이거 MySQL로 테이블 나눠서 JOIN 할까? 아니면 MongoDB로 한 Document에 다 넣을까?"

솔직히 그 순간 대답을 못 했어요. 😅

  • "NoSQL이 빠르다던데?"
  • "근데 RDBMS가 안정적이라고 하지 않았나?"
  • "둘 다 되긴 할 텐데, 뭘 기준으로 선택하지?"

그래서 이번 기회에 둘의 설계 방식이 어떻게 다른지, 언제 뭘 선택해야 하는지 제대로 공부해봤습니다!


🎯 Today I Learned

✅ RDBMS vs NoSQL(MongoDB) 핵심 차이
✅ ERD(Entity Relationship Diagram) 설계 방법
✅ 정규화란 무엇이고 왜 하는가
✅ MongoDB Document 구조 이해
✅ Embedding vs Referencing 선택 기준
✅ 같은 서비스를 두 방식으로 설계해보기 (실습)
✅ 언제 RDBMS, 언제 MongoDB를 선택할지

🤔 RDBMS와 MongoDB, 뭐가 다른 거야?

처음 느꼈던 혼란

MongoDB를 처음 접했을 때 이런 생각이 들었어요.

나의 혼란:

"테이블이 없다고? 그럼 데이터를 어디에 저장해?"
"JOIN이 없으면 연관된 데이터는 어떻게 가져와?"
"스키마가 없다는데, 그럼 아무 데이터나 막 넣어도 돼?"

핵심 차이를 이해하고 나서

둘의 철학 자체가 다르다는 걸 깨달았어요.

RDBMS (MySQL, PostgreSQL):
┌─────────────────────────────────────────────┐
│ "데이터를 정규화해서 중복 없이 저장하자!"      │
│                                             │
│ - 테이블로 구조화                            │
│ - 관계(Relationship)로 연결                 │
│ - JOIN으로 필요할 때 조합                   │
│ - 스키마가 엄격 (컬럼 타입 고정)             │
└─────────────────────────────────────────────┘

MongoDB (NoSQL):
┌─────────────────────────────────────────────┐
│ "자주 함께 쓰는 데이터는 함께 저장하자!"       │
│                                             │
│ - Document(JSON)로 저장                     │
│ - 필요하면 내장(Embedding)                  │
│ - JOIN 대신 한 번에 읽기                    │
│ - 스키마가 유연 (필드 자유롭게 추가)          │
└─────────────────────────────────────────────┘

비유로 이해하기:

RDBMS = 도서관 📚
- 책(데이터)은 분류 번호에 따라 정해진 위치에
- 찾으려면 색인(인덱스)으로 위치 확인 후 이동
- 책마다 양식이 정해져 있음 (ISBN, 저자, 출판사...)
- 관련 책은 "참고문헌"으로 연결

MongoDB = 개인 노트 📓
- 하나의 주제에 관련 내용 다 적어둠
- 필요한 건 그 페이지만 펼치면 됨
- 양식이 자유로움 (그림, 표, 글 섞어도 됨)
- 관련 내용은 같은 페이지에 함께

📐 ERD 설계 (관계형 DB)

ERD란?

**ERD(Entity Relationship Diagram)**는 데이터베이스의 구조를 시각적으로 표현한 설계도예요.

ERD의 구성 요소:

📦 Entity (엔티티)
   - 저장할 대상 (테이블이 됨)
   - 예: 회원, 게시글, 댓글, 주문

📝 Attribute (속성)
   - 엔티티의 특성 (컬럼이 됨)
   - 예: 회원의 이름, 이메일, 가입일

🔗 Relationship (관계)
   - 엔티티 간의 연결
   - 1:1, 1:N, N:M

실습: 쇼핑몰 ERD 설계

"회원이 상품을 주문한다"는 간단한 쇼핑몰을 설계해볼게요.

요구사항 분석:

1. 회원은 여러 주문을 할 수 있다 (1:N)
2. 하나의 주문에는 여러 상품이 포함될 수 있다 (N:M)
3. 상품은 하나의 카테고리에 속한다 (N:1)
ERD 설계 결과:

┌─────────────┐       ┌─────────────┐
│   Member    │       │  Category   │
├─────────────┤       ├─────────────┤
│ *member_id  │       │*category_id │
│  email      │       │ name        │
│  name       │       │ description │
│  created_at │       └──────┬──────┘
└──────┬──────┘              │ 1
       │ 1                   │
       │                     │
       │ N                   │ N
┌──────┴──────┐       ┌──────┴──────┐
│    Order    │       │   Product   │
├─────────────┤       ├─────────────┤
│ *order_id   │       │*product_id  │
│  member_id  │──┐    │ category_id │
│  order_date │  │    │ name        │
│  status     │  │    │ price       │
│  total_price│  │    │ stock       │
└──────┬──────┘  │    └──────┬──────┘
       │ 1       │           │
       │         │           │
       │ N       │           │ N
┌──────┴──────┐  │    ┌──────┴──────┐
│ Order_Item  │──┘    │             │
├─────────────┤       │             │
│*order_item_id       │(N:M 해소)   │
│ order_id    │───────┤             │
│ product_id  │───────┘             │
│ quantity    │                     │
│ price       │                     │
└─────────────┘

* = Primary Key (기본키)

정규화, 왜 하는 거야?

처음에 정규화가 왜 필요한지 이해가 안 됐어요.

정규화 전 (비정규형):

┌─────────────────────────────────────────────────────┐
│                      Orders                          │
├─────────────────────────────────────────────────────┤
│ order_id │ member_name │ member_email │ product_name│
├─────────────────────────────────────────────────────┤
│    1     │    홍길동    │ hong@mail.com│   맥북 프로  │
│    2     │    홍길동    │ hong@mail.com│   아이패드   │
│    3     │    홍길동    │ hong@mail.com│   에어팟     │
└─────────────────────────────────────────────────────┘

문제점:
❌ "홍길동" 정보가 3번 중복 저장
❌ 이메일 바꾸면 3군데 다 수정해야 함
❌ 하나만 수정하면 데이터 불일치 발생!
정규화 후:

┌─────────────────┐      ┌─────────────────┐
│     Members     │      │     Orders      │
├─────────────────┤      ├─────────────────┤
│ member_id: 1    │←─────│ member_id: 1    │
│ name: 홍길동    │      │ product: 맥북   │
│ email: hong@... │      ├─────────────────┤
└─────────────────┘      │ member_id: 1    │
                         │ product: 아이패드│
                         ├─────────────────┤
                         │ member_id: 1    │
                         │ product: 에어팟  │
                         └─────────────────┘

장점:
✅ 회원 정보는 한 곳에만 저장
✅ 이메일 바꾸면 한 군데만 수정
✅ 데이터 일관성 유지!

정규화의 핵심: "하나의 사실은 한 곳에만 저장한다!"

ERD 설계 시 내가 자주 하던 실수

실수 1: N:M 관계를 그대로 둠

❌ 잘못된 설계:
Order ──────── Product  (N:M 직접 연결)

✅ 올바른 설계:
Order ─── Order_Item ─── Product  (중간 테이블로 해소)

이유: RDBMS에서 N:M은 직접 표현 불가, 
     중간 테이블(Order_Item)이 필요!


실수 2: 모든 걸 한 테이블에

❌ 잘못된 설계:
Member 테이블에 주소1, 주소2, 주소3 컬럼

✅ 올바른 설계:
Member 테이블 + Address 테이블 분리 (1:N)

이유: 주소가 4개 이상 필요하면? 컬럼 추가? 
     유연성이 떨어짐!


실수 3: ID를 안 만듦

❌ 잘못된 설계:
Member 테이블의 PK를 email로 설정

✅ 올바른 설계:
member_id (auto increment) + email (unique)

이유: 이메일 변경 시 연관 테이블 전부 수정해야 함!
     비즈니스 값은 PK로 쓰지 않기!

📄 MongoDB Document 설계

Document란?

MongoDB는 테이블 대신 Collection, 행(Row) 대신 Document를 사용해요.

RDBMS 용어     →    MongoDB 용어
─────────────────────────────────
Database      →    Database
Table         →    Collection
Row           →    Document
Column        →    Field
Primary Key   →    _id
Foreign Key   →    Reference (또는 Embedding)
// MongoDB Document 예시
{
  "_id": ObjectId("507f1f77bcf86cd799439011"),
  "email": "hong@mail.com",
  "name": "홍길동",
  "profile": {
    "age": 28,
    "city": "서울"
  },
  "orders": [
    { "product": "맥북", "price": 2000000 },
    { "product": "아이패드", "price": 1000000 }
  ]
}

특징: JSON 형태로 저장되고, 중첩 구조가 가능해요!

Embedding vs Referencing

MongoDB 설계의 가장 중요한 결정!

Embedding (내장):
─────────────────
"관련 데이터를 한 Document에 같이 저장"

{
  "_id": "user1",
  "name": "홍길동",
  "addresses": [                    // 주소를 내장!
    { "city": "서울", "zip": "12345" },
    { "city": "부산", "zip": "67890" }
  ]
}

장점: 한 번의 쿼리로 다 가져옴 (빠름!)
단점: 중복 가능, Document 크기 제한(16MB)


Referencing (참조):
───────────────────
"다른 Document의 ID만 저장"

// users collection
{
  "_id": "user1",
  "name": "홍길동",
  "address_ids": ["addr1", "addr2"]   // ID만 저장
}

// addresses collection
{ "_id": "addr1", "city": "서울", "zip": "12345" }
{ "_id": "addr2", "city": "부산", "zip": "67890" }

장점: 중복 없음, 유연함
단점: 여러 번 쿼리 필요 (느릴 수 있음)

언제 Embedding? 언제 Referencing?

이게 제일 고민됐던 부분이에요. 정리해보니까 기준이 생기더라고요.

📌 Embedding이 좋은 경우:

✅ "함께 조회되는" 데이터
   예: 게시글 + 작성자 이름
   
✅ "소유 관계"인 데이터 (부모-자식)
   예: 주문 + 주문상품 (주문 없으면 주문상품도 의미 없음)
   
✅ 1:1 또는 1:Few 관계
   예: 사용자 + 프로필, 사용자 + 주소 (몇 개 안 됨)
   
✅ 자주 변경되지 않는 데이터
   예: 상품 정보의 스냅샷 (주문 당시 가격)


📌 Referencing이 좋은 경우:

✅ 독립적으로 조회되는 데이터
   예: 사용자 ↔ 게시글 (각각 따로 조회함)
   
✅ N:M 관계
   예: 학생 ↔ 수업
   
✅ 1:Many에서 Many가 많은 경우
   예: 작성자 ↔ 게시글 (게시글이 수천 개 될 수 있음)
   
✅ 자주 변경되는 데이터
   예: 상품 재고 (자주 바뀜, 중복 저장하면 동기화 지옥)

🔄 같은 서비스, 두 가지 설계

예시: 블로그 서비스

"게시글 + 댓글 + 좋아요" 기능을 두 방식으로 설계해볼게요.

요구사항:

  • 사용자는 게시글을 작성할 수 있다
  • 게시글에 댓글을 달 수 있다
  • 게시글에 좋아요를 누를 수 있다

RDBMS (MySQL) 설계

-- 테이블 4개로 분리 (정규화)

CREATE TABLE users (
    user_id BIGINT PRIMARY KEY AUTO_INCREMENT,
    email VARCHAR(255) UNIQUE,
    name VARCHAR(100),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE posts (
    post_id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id BIGINT,
    title VARCHAR(200),
    content TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(user_id)
);

CREATE TABLE comments (
    comment_id BIGINT PRIMARY KEY AUTO_INCREMENT,
    post_id BIGINT,
    user_id BIGINT,
    content TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (post_id) REFERENCES posts(post_id),
    FOREIGN KEY (user_id) REFERENCES users(user_id)
);

CREATE TABLE likes (
    like_id BIGINT PRIMARY KEY AUTO_INCREMENT,
    post_id BIGINT,
    user_id BIGINT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    UNIQUE KEY unique_like (post_id, user_id),
    FOREIGN KEY (post_id) REFERENCES posts(post_id),
    FOREIGN KEY (user_id) REFERENCES users(user_id)
);
게시글 + 댓글 + 좋아요 수 조회:

SELECT p.*, u.name as author_name,
       (SELECT COUNT(*) FROM comments c WHERE c.post_id = p.post_id) as comment_count,
       (SELECT COUNT(*) FROM likes l WHERE l.post_id = p.post_id) as like_count
FROM posts p
JOIN users u ON p.user_id = u.user_id
WHERE p.post_id = 1;

→ JOIN 필요, 서브쿼리 필요
→ 테이블 여러 개 접근

MongoDB 설계

// posts collection - 하나의 Document에 관련 데이터 내장

{
  "_id": ObjectId("..."),
  "title": "오늘의 TIL",
  "content": "오늘 배운 내용...",
  "author": {                         // 작성자 정보 내장
    "user_id": ObjectId("..."),
    "name": "홍길동"                   // 자주 쓰는 정보만!
  },
  "comments": [                        // 댓글 내장
    {
      "comment_id": ObjectId("..."),
      "user_id": ObjectId("..."),
      "user_name": "김철수",
      "content": "좋은 글이네요!",
      "created_at": ISODate("...")
    },
    {
      "comment_id": ObjectId("..."),
      "user_id": ObjectId("..."),
      "user_name": "이영희",
      "content": "잘 읽었습니다",
      "created_at": ISODate("...")
    }
  ],
  "likes": [                           // 좋아요 누른 사용자 ID 배열
    ObjectId("user1"),
    ObjectId("user2"),
    ObjectId("user3")
  ],
  "like_count": 3,                     // 미리 계산해서 저장
  "comment_count": 2,
  "created_at": ISODate("...")
}
// 게시글 + 댓글 + 좋아요 수 조회:

db.posts.findOne({ _id: ObjectId("...") })

→ 쿼리 한 번에 다 가져옴!
→ JOIN 없음

두 설계의 트레이드오프

RDBMS 설계:
───────────
장점:
✅ 데이터 중복 없음 (사용자 이름 한 곳에만)
✅ 수정이 쉬움 (이름 바꾸면 한 군데만)
✅ 복잡한 쿼리 가능 (GROUP BY, 통계 등)
✅ 데이터 정합성 보장 (FK 제약조건)

단점:
❌ JOIN이 많아지면 느려짐
❌ 스키마 변경이 어려움 (ALTER TABLE...)
❌ 수평 확장이 어려움


MongoDB 설계:
─────────────
장점:
✅ 읽기가 빠름 (한 번에 다 가져옴)
✅ 스키마 유연 (필드 자유롭게 추가)
✅ 수평 확장 용이 (샤딩)

단점:
❌ 데이터 중복 (사용자 이름이 여기저기)
❌ 이름 바꾸면 여러 Document 수정 필요
❌ 복잡한 집계는 RDBMS보다 어려움
❌ Document 크기 제한 (16MB)

🤔 그래서 언제 뭘 써야 해?

이런 고민을 했었어요

"MongoDB가 빠르다면서? 그냥 다 MongoDB 쓰면 안 돼?"

→ 아니에요! 상황에 따라 달라요.

선택 기준 정리

📌 RDBMS를 선택해야 할 때:

✅ 데이터 정합성이 중요할 때
   예: 금융, 결제, 재고 관리
   "잔액이 100원인데 200원 출금되면 안 돼!"

✅ 복잡한 관계와 JOIN이 많을 때
   예: ERP, CRM 시스템
   여러 테이블의 데이터를 조합해서 리포트 생성

✅ 트랜잭션이 중요할 때
   예: 주문 처리 (주문 생성 + 재고 감소 + 결제 처리)
   "하나라도 실패하면 전부 롤백!"

✅ 스키마가 명확하고 잘 안 변할 때
   예: 회계 시스템, 인사 시스템


📌 MongoDB를 선택해야 할 때:

✅ 읽기 성능이 중요할 때
   예: 콘텐츠 서비스, 블로그, SNS 피드
   "게시글 + 댓글 + 좋아요를 빠르게!"

✅ 스키마가 자주 변할 때
   예: 스타트업 MVP, 빠르게 변하는 서비스
   "이번 주에 필드 3개 추가해야 해요"

✅ 대용량 데이터 + 수평 확장이 필요할 때
   예: 로그 데이터, IoT 센서 데이터
   서버 여러 대로 분산 저장

✅ 비정형 데이터가 많을 때
   예: 상품마다 속성이 다른 이커머스
   "옷은 사이즈, 전자제품은 전압, 식품은 유통기한..."

실제로는 둘 다 쓰기도 해요!

Polyglot Persistence (다중 저장소):

┌─────────────────────────────────────────┐
│              쇼핑몰 서비스               │
├─────────────────────────────────────────┤
│                                         │
│  [MySQL]              [MongoDB]         │
│  - 회원 정보          - 상품 카탈로그    │
│  - 주문/결제          - 상품 리뷰        │
│  - 재고 관리          - 검색 로그        │
│                                         │
│  → 정합성 중요!       → 유연성/속도!    │
│                                         │
│              [Redis]                    │
│              - 세션                     │
│              - 캐시                     │
│                                         │
└─────────────────────────────────────────┘

→ 각 DB의 장점을 살려서 조합!

🛠️ 설계할 때 내가 겪은 실수들

실수 1: "일단 MongoDB니까 다 Embedding"

처음 생각:
"MongoDB는 Embedding이 장점이라며? 다 내장하자!"

// 잘못된 설계 - 작성자 정보를 통째로 내장
{
  "title": "게시글",
  "author": {
    "user_id": "...",
    "name": "홍길동",
    "email": "hong@mail.com",      // 이것도?
    "phone": "010-1234-5678",      // 이것도?
    "address": "서울시...",         // 이것까지?
    "profile_image": "base64..."   // 이미지까지??
  }
}

문제:
❌ Document가 너무 커짐
❌ 작성자 정보 바뀌면 모든 게시글 수정해야 함
❌ 이메일 같은 민감정보가 여기저기 복제됨
올바른 설계:
"자주 함께 조회되는 최소한의 정보만 내장"

{
  "title": "게시글",
  "author": {
    "user_id": ObjectId("..."),    // 필수
    "name": "홍길동"                // 화면에 보여줄 것만!
  }
}

// 상세 정보가 필요하면 user_id로 별도 조회

실수 2: "정규화는 무조건 좋은 거야"

처음 생각:
"정규화를 많이 할수록 좋은 설계야!"

// 과도한 정규화
Users → UserProfiles → UserAddresses → Cities → Countries

문제:
❌ 사용자 정보 조회하는데 JOIN 5번?
❌ 성능 저하
❌ 쿼리 복잡해짐
적절한 균형:
"읽기 패턴을 고려해서 적당히"

// 자주 함께 조회되면 같은 테이블에
Users (id, name, email, city, country)

// 독립적으로 관리되면 분리
Users + Addresses (1:N)

실수 3: "ERD 없이 바로 코딩"

처음:
"설계? 그냥 코딩하면서 필요할 때 테이블 만들면 되지!"

3개월 후:
"어... 이 테이블이랑 저 테이블이 어떤 관계지?"
"이 컬럼 왜 있는 거야?"
"같은 데이터가 왜 두 군데 있어?"
배운 점:
✅ 시작 전에 ERD 그리기 (30분 투자로 3시간 절약)
✅ 팀원과 공유하면 소통이 쉬워짐
✅ 나중에 유지보수할 때 큰 도움

💡 오늘 배운 핵심 정리

1️⃣ 철학이 다르다
   RDBMS: "중복 없이 정규화해서 저장"
   MongoDB: "함께 쓰는 데이터는 함께 저장"

2️⃣ 설계 방식이 다르다
   RDBMS: ERD로 테이블과 관계 정의 → JOIN으로 조합
   MongoDB: Document 구조 설계 → Embedding or Referencing

3️⃣ 선택 기준
   정합성/트랜잭션 중요 → RDBMS
   유연성/읽기 속도 중요 → MongoDB

4️⃣ 정답은 없다
   서비스 특성에 따라 선택
   하나의 서비스에서 둘 다 쓰기도 함

5️⃣ 설계가 중요하다
   어떤 DB를 쓰든, 먼저 설계를 잘 해야 함
   ERD든 Document 구조든, 그림 그려보기!

😊 좋았던 점 & 😅 아쉬웠던 점

좋았던 점

👍 선택의 기준이 생김

"MongoDB가 빠르대" 같은 막연한 이야기 대신, 구체적인 선택 기준이 생겼어요. 이제 프로젝트 시작할 때 뭘 써야 할지 근거 있게 결정할 수 있어요!

👍 ERD의 중요성 체감

설계 없이 코딩하다가 꼬인 경험이 있어서, ERD 먼저 그리는 습관의 소중함을 알게 됐어요.

👍 Embedding vs Referencing 기준 정리

MongoDB 쓸 때 가장 고민되던 부분이었는데, 이제 어느 정도 기준이 생겼어요.

아쉬웠던 점

😢 인덱스 설계는 더 공부 필요

테이블/Document 구조는 알겠는데, 성능을 위한 인덱스 설계는 아직 부족해요.

😢 실제 대용량 데이터 경험 부족

이론적으로는 이해했는데, 실제로 수백만 건 데이터를 다뤄본 경험이 없어서 체감이 부족해요.


🚀 다음 학습 목표

📌 단기
   ✓ 프로젝트에 ERD 적용
   ✓ MongoDB 실습 (간단한 CRUD)

📌 중기
   ✓ 인덱스 설계 학습
   ✓ 쿼리 성능 최적화

📌 장기
   ✓ 실제 서비스에서 DB 선택 경험
   ✓ 샤딩, 레플리카 등 분산 DB 학습

🎬 마치며

데이터베이스 설계를 공부하면서 가장 크게 느낀 건, **"정답이 없다"**는 거예요.

RDBMS가 좋을 때도 있고, MongoDB가 좋을 때도 있어요. 심지어 하나의 서비스에서 둘 다 쓰기도 하고요.

중요한 건 "왜 이걸 선택했는지" 설명할 수 있는 거예요.

  • "우리 서비스는 결제가 핵심이라 트랜잭션이 중요해서 MySQL 썼어요"
  • "상품 속성이 다양해서 스키마 유연한 MongoDB 썼어요"

이렇게 근거 있는 선택을 할 수 있게 된 게 오늘의 가장 큰 수확이에요! 💪


📚 참고 자료