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

액세스 패턴, 성장, 일관성 및 업데이트 동작에 따라 내장 또는 참조 MongoDB 문서를 선택하세요.

올바른 MongoDB 데이터 모델 선택: 내장 문서 vs 참조 문서

MongoDB 데이터 모델링은 일반적으로 한 가지 실용적인 질문으로 귀결됩니다: 관련 데이터가 동일한 문서 안에 있어야 할까요, 아니면 한 문서가 다른 문서를 참조해야 할까요? 이 선택은 읽기 속도, 업데이트 비용, 문서 성장, 그리고 애플리케이션이 처리해야 할 일관성 작업의 양에 영향을 미칩니다.

이 가이드는 부모 문서 내에 문서를 내장하는 것과 다른 컬렉션 간에 관련 문서를 참조하는 것 사이의 트레이드오프를 깊이 있게 다룹니다. 이러한 기술을 언제, 어떻게 적용해야 하는지 이해하면 애플리케이션의 특정 액세스 패턴에 맞춰 효율적이고 고성능의 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 또는 다른 안정적인 식별자를 저장합니다. 이는 종종 관련 데이터를 검색하기 위해 두 번째 쿼리, $lookup 집계 단계 또는 애플리케이션 측 조인이 필요합니다.

참조를 사용해야 하는 경우

다음과 같은 경우 참조 패턴을 사용하세요:

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

참조 유형

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

부모 문서에 _id를 저장:

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

// Book 컬렉션
{
  "_id": ObjectId("book456"),
  "title": "데이터 모델링 101",
  "author_id": ObjectId("author123") // 참조
}

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

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

B. 양방향 참조

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

참조의 단점

  1. 쿼리 복잡성 증가: 완전히 비정규화된 데이터를 검색하려면 조인이 필요하며(애플리케이션 코드 또는 MongoDB의 $lookup을 통해), 이는 단일 내장 읽기 작업보다 느릴 수 있습니다.
  2. 일관성 관리: 책 레코드에 저자의 표시 이름과 같이 참조된 문서에서 선택한 필드를 복제하는 경우 해당 복사본을 업데이트하거나 일시적인 부실을 수용해야 합니다.

요약: 올바른 선택하기

내장과 참조 사이의 결정은 액세스 패턴에 달려 있습니다. 스스로에게 물어보세요: 이 관련 데이터가 얼마나 자주 검색됩니까? 얼마나 자주 변경됩니까? 작거나 잠재적으로 방대합니까?

특징 / 고려 사항 내장(비정규화) 참조(정규화)
읽기 성능 우수(단일 쿼리) 좋음~보통(조인 필요)
쓰기 성능 크거나 핫한 부모 문서의 경우 비용이 많이 들 수 있음 독립적인 문서의 경우 종종 더 간단함
데이터 크기 제한 16MB 문서 제한에 의해 제한됨 하나의 거대한 부모 문서를 피하지만 인덱스와 쿼리 제한을 신중하게 설계해야 함
관계 유형 일대소수 일대다, 다대다
데이터 일관성 높음(원자적 쓰기) 수동으로 관리(잠재적 부실)

모범 사례 팁: 내장으로 시작하고 나중에 전환

일반적이고 효과적인 전략은 자주 함께 읽는 데이터를 내장하는 것부터 시작하는 것입니다. 이는 일반적인 경우에 최적화됩니다. 나중에 문서 성장이나 과도한 업데이트 복잡성으로 인한 성능 병목 현상이 발생하면 해당 특정 데이터를 자체 컬렉션으로 전환하고 참조로 변경할 수 있습니다.

결론

MongoDB 스키마는 실제 쿼리와 일치할 때 가장 잘 작동합니다. 함께 읽고 경계를 유지할 수 있는 데이터는 내장하세요. 독립적으로 성장하거나, 여러 부모가 공유하거나, 자체 일정에 따라 변경되는 데이터는 참조하세요. 모델을 결정하기 전에 주요 읽기 및 쓰기 작업을 기록하고, 현실적인 문서 크기로 해당 경로를 테스트하세요.