MongoDB 참조 사용법

MongoDBBeginner
지금 연습하기

소개

이 랩에서는 MongoDB 참조를 사용하여 데이터 간의 관계를 모델링하는 방법을 배웁니다. authorsbooks 컬렉션을 사용하여 간단한 도서관 관리 시스템을 구축합니다. 실습을 통해 문서를 생성하고, 참조를 사용하여 연결하고, 컬렉션 간 관련 데이터를 쿼리하고, 이러한 참조를 업데이트하고, 인덱스를 사용하여 쿼리 성능을 개선하는 방법을 배우게 됩니다. 이 랩은 MongoDB 에서의 데이터 모델링에 대한 실질적인 기초를 제공합니다.

컬렉션 및 참조 문서 생성

이 단계에서는 데이터베이스를 설정하고 authorsbooks라는 두 개의 컬렉션을 생성합니다. 책을 저자에게 연결하여 문서 참조의 기본 개념을 배우게 됩니다.

먼저 MongoDB Shell 을 엽니다. 이 대화형 셸에서 모든 데이터베이스 명령을 실행합니다.

mongosh

셸에 들어가면 test> 프롬프트가 표시됩니다. library_db라는 새 데이터베이스로 전환합니다. 데이터베이스가 존재하지 않으면 MongoDB 는 데이터를 처음 저장할 때 데이터베이스를 생성합니다.

use library_db

이제 첫 번째 저자를 생성합니다. authors 컬렉션에 문서를 삽입합니다. 나중에 쉽게 참조할 수 있도록 이 저자에 대한 사용자 지정 _id를 지정합니다.

db.authors.insertOne({
    _id: ObjectId("6633c9a5b4e3e8a5c8a8f8b1"),
    name: "Jane Austen",
    nationality: "British",
    birthYear: 1775
})

다음으로 books 컬렉션에 문서를 삽입합니다. author_id 필드에는 방금 생성한 저자의 ObjectId가 포함됩니다. 이것이 참조를 생성하는 방법입니다.

db.books.insertOne({
    title: "Pride and Prejudice",
    author_id: ObjectId("6633c9a5b4e3e8a5c8a8f8b1"),
    published: 1813,
    genre: "Classic Literature"
})

이제 일대일 관계를 생성했습니다. 이를 확인하기 위해 방금 생성한 문서를 검색할 수 있습니다.

먼저 저자를 찾습니다.

db.authors.findOne({ name: "Jane Austen" })

예시 출력:

{
  _id: ObjectId("6633c9a5b4e3e8a5c8a8f8b1"),
  name: 'Jane Austen',
  nationality: 'British',
  birthYear: 1775
}

이제 책을 찾고 저자를 연결하는 author_id 필드를 확인합니다.

db.books.findOne({ title: "Pride and Prejudice" })

예시 출력:

{
  _id: ObjectId("..."),
  title: 'Pride and Prejudice',
  author_id: ObjectId("6633c9a5b4e3e8a5c8a8f8b1"),
  published: 1813,
  genre: 'Classic Literature'
}

다음 단계를 위해 mongosh 셸에 계속 머물러 있을 수 있습니다.

여러 문서 연결

저자는 일반적으로 한 권 이상의 책을 씁니다. 이 단계에서는 여러 "자식" 문서 (책) 를 단일 "부모" 문서 (저자) 에 연결하는 방법을 배웁니다. 이는 일대다 관계를 보여줍니다.

mongosh 셸에서 계속 작업합니다. Jane Austen 의 책 두 권을 더 추가해 보겠습니다. insertMany 명령을 사용하여 여러 문서를 한 번에 삽입합니다. 새로 추가되는 두 권의 책 모두 동일한 author_id를 참조합니다.

db.books.insertMany([
    {
        title: "Sense and Sensibility",
        author_id: ObjectId("6633c9a5b4e3e8a5c8a8f8b1"),
        published: 1811,
        genre: "Classic Literature"
    },
    {
        title: "Emma",
        author_id: ObjectId("6633c9a5b4e3e8a5c8a8f8b1"),
        published: 1815,
        genre: "Classic Literature"
    }
])

이제 Jane Austen 의 책 세 권이 데이터베이스에 있으므로 find() 메서드를 사용하고 author_id로 필터링하여 모두 검색합니다.

db.books.find({ author_id: ObjectId("6633c9a5b4e3e8a5c8a8f8b1") })

예시 출력:

[
  {
    _id: ObjectId("..."),
    title: 'Pride and Prejudice',
    author_id: ObjectId("6633c9a5b4e3e8a5c8a8f8b1"),
    published: 1813,
    genre: 'Classic Literature'
  },
  {
    _id: ObjectId("..."),
    title: 'Sense and Sensibility',
    author_id: ObjectId("6633c9a5b4e3e8a5c8a8f8b1"),
    published: 1811,
    genre: 'Classic Literature'
  },
  {
    _id: ObjectId("..."),
    title: 'Emma',
    author_id: ObjectId("6633c9a5b4e3e8a5c8a8f8b1"),
    published: 1815,
    genre: 'Classic Literature'
  }
]

countDocuments를 사용하여 특정 저자와 연결된 책의 수를 빠르게 셀 수도 있습니다.

db.books.countDocuments({ author_id: ObjectId("6633c9a5b4e3e8a5c8a8f8b1") })

예시 출력:

3

이 간단한 쿼리는 연결된 문서 수를 효율적으로 확인합니다.

$lookup 을 사용한 컬렉션 간 쿼리

지금까지는 알려진 author_id를 사용하여 책을 검색했습니다. 더 강력한 접근 방식은 단일 쿼리에서 두 컬렉션의 데이터를 결합하는 것입니다. 이 단계에서는 $lookup 집계 단계를 사용하여 books 컬렉션에서 authors 컬렉션으로 왼쪽 외부 조인 (left outer join) 을 수행합니다.

먼저 쿼리를 더 흥미롭게 만들기 위해 다른 저자와 책을 추가합니다.

db.authors.insertOne({
    _id: ObjectId("6633c9a5b4e3e8a5c8a8f8b2"),
    name: "Charles Dickens",
    nationality: "British",
    birthYear: 1812
})
db.books.insertOne({
    title: "Oliver Twist",
    author_id: ObjectId("6633c9a5b4e3e8a5c8a8f8b2"),
    published: 1837,
    genre: "Historical Fiction"
})

이제 집계 파이프라인을 구성합니다. 이 쿼리는 books 컬렉션에서 시작하여 각 책에 대한 일치하는 저자를 "조회"합니다.

db.books.aggregate([
    {
        $lookup: {
            from: "authors",
            localField: "author_id",
            foreignField: "_id",
            as: "author_details"
        }
    }
])

$lookup 단계는 다음과 같은 필드를 가집니다.

  • from: "authors": 조인할 컬렉션을 지정합니다.
  • localField: "author_id": 입력 문서 ( books 컬렉션) 의 필드입니다.
  • foreignField: "_id": "from" 컬렉션 ( authors 컬렉션) 의 문서 필드입니다.
  • as: "author_details": 입력 문서에 추가되는 새 배열 필드의 이름입니다.

예시 출력 (문서 하나에 대한):

{
  _id: ObjectId("..."),
  title: 'Pride and Prejudice',
  author_id: ObjectId("6633c9a5b4e3e8a5c8a8f8b1"),
  published: 1813,
  genre: 'Classic Literature',
  author_details: [
    {
      _id: ObjectId("6633c9a5b4e3e8a5c8a8f8b1"),
      name: 'Jane Austen',
      nationality: 'British',
      birthYear: 1775
    }
  ]
}

보시다시피, 저자의 정보가 이제 author_details 필드 아래 각 책 문서에 포함되었습니다. 이를 통해 두 컬렉션의 필드를 동시에 쿼리할 수 있습니다.

참조 업데이트 및 유지 관리

데이터는 항상 고정된 것이 아닙니다. 오류를 수정하거나 데이터를 제거해야 할 수 있으며, 이를 위해서는 문서 및 해당 참조를 업데이트하거나 삭제해야 합니다. 이 단계에서는 참조를 업데이트하고 "고아" 문서를 처리하는 방법을 배웁니다.

"Emma"라는 책이 실수로 Jane Austen 에게 잘못 할당되었으며 Charles Dickens 에게 할당되어야 한다는 사실을 발견했다고 가정해 보겠습니다. $set 연산자와 함께 updateOne 명령을 사용하여 이를 수정할 수 있습니다.

db.books.updateOne(
    { title: "Emma" },
    { $set: { author_id: ObjectId("6633c9a5b4e3e8a5c8a8f8b2") } }
)

책을 다시 찾고 author_id를 확인하여 변경 사항을 검증합니다.

db.books.findOne({ title: "Emma" })

예시 출력:

{
  _id: ObjectId("..."),
  title: 'Emma',
  author_id: ObjectId("6633c9a5b4e3e8a5c8a8f8b2"),
  published: 1815,
  genre: 'Classic Literature'
}

이제 부모 문서가 삭제될 때 어떤 일이 발생하는지 살펴보겠습니다. 저자를 삭제하면 해당 저자를 참조하는 모든 책이 "고아"가 됩니다. Charles Dickens 를 데이터베이스에서 삭제해 보겠습니다.

db.authors.deleteOne({ name: "Charles Dickens" })

저자 문서는 사라졌지만, "Emma"와 "Oliver Twist" 책은 여전히 삭제된 저자를 가리키는 author_id를 가지고 있습니다. 이는 데이터 무결성 문제를 야기할 수 있습니다. 실제 애플리케이션에서는 고아 책을 삭제하거나 재할당하는 것과 같이 이를 처리하는 로직을 구현해야 합니다.

이 실습에서는 두 개의 고아 책을 수동으로 정리하겠습니다.

db.books.deleteMany({ author_id: ObjectId("6633c9a5b4e3e8a5c8a8f8b2") })

이 명령은 이제 삭제된 저자를 참조하는 books 컬렉션의 모든 문서를 제거하여 데이터의 일관성을 유지합니다.

인덱스를 사용한 쿼리 성능 향상

컬렉션이 커지면 특정 필드로 필터링하는 쿼리가 느려질 수 있습니다. 이는 MongoDB 가 일치하는 항목을 찾기 위해 모든 문서를 스캔해야 하기 때문입니다. 이를 최적화하기 위해 자주 쿼리하는 필드에 인덱스를 생성할 수 있습니다. 우리의 경우 books 컬렉션의 author_id가 완벽한 후보입니다.

이 단계에서는 저자의 책에 대한 조회를 더 빠르게 하기 위해 author_id 필드에 인덱스를 생성합니다.

books 컬렉션에서 createIndex 명령을 사용합니다. 인수 { author_id: 1 }은 MongoDB 에 author_id 필드에 대한 오름차순 인덱스를 생성하도록 지시합니다.

db.books.createIndex({ author_id: 1 })

MongoDB 는 이를 백그라운드에서 처리합니다. 완료되면 인덱스가 생성되었음을 확인하는 메시지를 반환합니다.

예시 출력:

{
  "numIndexesBefore": 1,
  "numIndexesAfter": 2,
  "createdCollectionAutomatically": false,
  "ok": 1
}

인덱스가 존재하는지 확인하기 위해 getIndexes 명령을 사용할 수 있습니다. 이 명령은 books 컬렉션의 모든 인덱스를 나열합니다.

db.books.getIndexes()

_id에 대한 기본 인덱스와 방금 생성한 새 author_id_1 인덱스의 두 가지 인덱스를 볼 수 있어야 합니다.

예시 출력:

[
  { "v": 2, "key": { "_id": 1 }, "name": "_id_" },
  { "v": 2, "key": { "author_id": 1 }, "name": "author_id_1" }
]

이 인덱스가 있으면 author_id로 필터링하거나 정렬하는 모든 쿼리, 이전에 사용했던 $lookup 단계도 포함하여 대규모 데이터셋에서 훨씬 더 빠르게 실행됩니다.

마지막으로 MongoDB 셸을 종료할 수 있습니다.

exit

요약

이 실습에서는 MongoDB 에서 문서 참조를 사용하는 기본 사항을 배웠습니다. 컬렉션을 생성하고 ObjectId 참조로 문서를 연결하는 것부터 시작했습니다. 그런 다음 일대다 관계를 관리하고, 강력한 $lookup 집계 단계를 사용하여 컬렉션 간에 쿼리하고, 참조를 업데이트하고 정리하여 데이터 무결성을 유지하는 연습을 했습니다. 마지막으로 참조 필드에 인덱스를 생성하여 쿼리 성능을 향상시켰습니다. 이러한 기술은 MongoDB 를 사용하여 확장 가능하고 효율적인 애플리케이션을 구축하는 데 필수적입니다.