올바른 MongoDB 데이터 모델 선택: 임베디드 문서 대 참조 문서

데이터 모델링을 숙달하여 최적의 MongoDB 성능을 잠금 해제하세요. 이 가이드에서는 빠른 읽기를 위해 관련 데이터를 임베딩(비정규화)하는 것과 크고 동적이거나 공유되는 관계를 처리하기 위해 문서를 참조하는 것 사이의 중요한 장단점을 자세히 설명합니다. 확장 가능하고 효율적인 NoSQL 애플리케이션을 구축하기 위해 각 기술을 적용해야 하는 시점과 실용적인 전략을 배우십시오.

42 조회수

올바른 MongoDB 데이터 모델 선택: 임베딩 vs. 참조 문서

문서 데이터베이스인 MongoDB의 유연성 덕분에 개발자는 다양한 방식으로 데이터 간의 관계를 모델링할 수 있습니다. 정규화된 스키마를 엄격하게 적용하는 기존 관계형 데이터베이스와 달리, MongoDB는 컬렉션 내에서 관련 데이터를 구조화하기 위한 두 가지 주요하고 강력한 전략을 제공합니다: 임베딩(Embedding)참조(Referencing). 올바른 접근 방식을 선택하는 것은 애플리케이션 성능, 데이터 일관성, 쿼리 복잡성 및 확장성에 직접적인 영향을 미치므로 매우 중요합니다.

이 가이드는 부모 문서 내에 문서를 임베딩하는 것과 다른 컬렉션에 걸쳐 관련 문서를 참조하는 것 사이의 장단점을 심층적으로 분석합니다. 이러한 기술을 언제, 어떻게 적용해야 하는지 이해하면 애플리케이션의 특정 액세스 패턴에 맞춰 효율적이고 고성능의 MongoDB 스키마를 설계할 수 있습니다.


MongoDB 데이터 모델링 전략 이해하기

MongoDB는 컬렉션에 저장되는 문서(JSON 객체와 유사)에 데이터를 구성합니다. 이러한 문서 간의 관계는 두 가지 핵심 패턴을 사용하여 모델링할 수 있습니다:

  1. 임베딩 (비정규화): 관련 데이터를 부모 문서 내부에 직접 저장합니다.
  2. 참조 (정규화): 관련 문서에 대한 참조(예: _id)만 다른 컬렉션에 저장하며, 이는 외래 키와 유사합니다.

1. 임베딩 패턴 (비정규화)

임베딩은 한 문서를 다른 문서 내부에 직접 배치하는 것을 포함합니다. 이 기술은 데이터 관계가 일대소수(one-to-few)이거나 관련 데이터가 부모 문서와 함께 자주 액세스될 때 MongoDB에서 매우 선호됩니다.

임베딩을 사용해야 하는 경우

다음과 같은 경우 임베딩 패턴을 사용하십시오:

  • 데이터가 함께 액세스될 때: 부모를 쿼리할 때 관련 데이터가 거의 항상 필요하다면, 임베딩은 전체 정보 세트를 가져오는 데 필요한 데이터베이스 작업을 최소화합니다.
  • 일대소수 관계: 임베딩된 문서 배열이 비교적 작고 예측 가능하게 유지되는 관계(예: 사용자의 최근 10개 로그인 활동 또는 주문의 품목별 내역)에 이상적입니다.
  • 데이터 일관성이 중요할 때: 임베딩된 데이터는 단일 문서 내에 존재하므로 본질적으로 일관성이 있으며, MongoDB의 단일 문서 ACID 트랜잭션에서 제공하는 원자성 보장을 단순화합니다.

임베딩 예시

Product와 해당 Reviews(리뷰)를 고려해 보겠습니다. 리뷰가 제품과 함께 자주 가져와지고 총 리뷰 수가 관리 가능한 경우:

// Product 컬렉션 문서
{
  "_id": ObjectId("..."),
  "name": "고성능 SSD",
  "price": 129.99,
  "reviews": [
    {
      "user": "Alice",
      "rating": 5,
      "comment": "가장 빠른 드라이브!"
    },
    {
      "user": "Bob",
      "rating": 4,
      "comment": "가성비 최고."
    }
  ]
}

임베딩의 단점

  1. 문서 크기 제한: MongoDB 문서는 최대 크기 제한이 16MB입니다. 임베딩된 문서의 배열이 무제한으로 증가하면 결국 이 제한에 도달하여 참조로 전환해야 합니다.
  2. 업데이트 오버헤드: 단일 임베딩된 요소를 업데이트하려면 전체 부모 문서를 다시 작성해야 하는데, 부모 문서가 매우 큰 경우 비효율적일 수 있습니다.
  3. 데이터 중복: 임베딩된 데이터를 부모와 독립적으로 공유하거나 표시해야 하는 경우, 모든 사본에 업데이트가 동기화되지 않으면 데이터 중복 및 잠재적인 일관성 문제가 발생할 수 있습니다.

2. 참조 패턴 (정규화)

참조는 관계형 데이터베이스의 외래 키 개념을 모방합니다. 관련 데이터를 임베딩하는 대신, 부모 문서에 관련 문서의 _id(또는 ID 조합)를 저장합니다. 이 경우 실제 관련 데이터를 검색하기 위해 두 번째 쿼리( $lookup 집계 단계 또는 애플리케이션 측 조인)가 필요합니다.

참조를 사용해야 하는 경우

다음과 같은 경우 참조 패턴을 사용하십시오:

  • 일대다 또는 다대다 관계: 관계의 한쪽이 무한정 증가할 수 있는 경우(예: 블로그 게시물에 대한 댓글 수 또는 여러 그룹에 속한 사용자).
  • 여러 부모에 걸쳐 공유되는 데이터: 관련 데이터 엔터티가 여러 다른 문서에 의해 독립적으로 업데이트되고 액세스되어야 하는 경우(예: 여러 Product 문서에서 사용하는 Category 문서).
  • 대규모 데이터 세트: 임베딩이 16MB 문서 크기 제한을 위반하는 경우.

참조 유형

A. 수동 참조 (애플리케이션 측 조인)

부모 문서에 _id를 저장합니다:

// Author 컬렉션
{
  "_id": ObjectId("author123"),
  "name": "Jane Doe"
}

// Book 컬렉션
{
  "_id": ObjectId("book456"),
  "title": "Data Modeling 101",
  "author_id": ObjectId("author123") // 참조
}

저자 이름을 검색하려면 두 번의 쿼리를 실행하거나 $lookup을 사용해야 합니다:

// 집계 프레임워크에서 $lookup을 사용하는 예시
db.books.aggregate([
  { $match: { title: "Data Modeling 101" } },
  {
    $lookup: {
      from: "authors",          // 조인할 컬렉션
      localField: "author_id",  // 입력 문서(books)의 필드
      foreignField: "_id",      // 'from' 컬렉션(authors) 문서의 필드
      as: "author_details"
    }
  }
]);

B. 양방향 참조

양방향 관계의 경우, 자식 문서에서도 부모를 참조할 수 있습니다. 이렇게 하면 양방향으로 관계를 탐색하기가 더 쉬워지지만, 두 위치에서 업데이트가 발생해야 하므로 쓰기 오버헤드가 증가합니다.

참조의 단점

  1. 쿼리 복잡성 증가: 완전히 비정규화된 데이터를 검색하려면 조인(애플리케이션 코드 또는 MongoDB의 $lookup을 통해)이 필요하며, 이는 단일 임베딩 읽기 작업보다 느릴 수 있습니다.
  2. 일관성 관리: 참조된 데이터(예: 저자 이름 변경)를 변경하는 경우, 해당 저자를 참조하는 모든 문서를 수동으로 업데이트하거나 새로 고침될 때까지 일부 문서가 오래된 데이터를 표시하는 것을 감수해야 합니다.

요약: 올바른 선택하기

임베딩과 참조 사이의 결정은 액세스 패턴을 중심으로 이루어집니다. 스스로에게 질문하십시오: 이 관련 데이터는 얼마나 자주 검색되는가? 얼마나 자주 변경되는가? 작거나 잠재적으로 방대한가?

기능 / 고려 사항 임베딩 (비정규화) 참조 (정규화)
읽기 성능 우수 (단일 쿼리) 양호에서 보통 (조인 필요)
쓰기 성능 나쁨 (전체 문서 재작성) 양호 (참조 지점만 업데이트)
데이터 크기 제한 16MB로 제한됨 실질적인 제한 없음
관계 유형 일대소수 일대다, 다대다
데이터 일관성 높음 (원자적 쓰기) 수동 관리 (잠재적 최신성 부족)

모범 사례 팁: 임베딩으로 시작하고 나중에 전환

일반적이고 효과적인 전략은 함께 자주 읽는다는 것을 아는 데이터를 먼저 임베딩하는 것입니다. 이는 일반적인 경우에 최적화됩니다. 나중에 대용량 문서 증가 또는 과도한 업데이트 복잡성으로 인해 성능 병목 현상이 발생하면 해당 특정 데이터를 별도의 컬렉션으로 전환하고 참조로 변경할 수 있습니다.

결론

MongoDB는 애플리케이션 요구 사항에 따라 읽기 또는 쓰기에 최적화할 수 있는 유연성을 제공합니다. 임베딩은 데이터가 긴밀하게 결합되어 있을 때 빠른 읽기 액세스를 위해 업데이트 단순성을 희생합니다. 참조는 더 복잡한 읽기 작업을 수반하는 비용으로 데이터 무결성을 보존하고 무제한 성장을 처리합니다. 애플리케이션의 읽기/쓰기 비율 및 관계 카디널리티를 신중하게 분석함으로써 성능과 유지 관리 용이성을 극대화하는 MongoDB 스키마를 설계할 수 있습니다.