소개
이 랩에서는 MongoDB 참조를 사용하여 데이터 간의 관계를 모델링하는 방법을 배웁니다. authors 및 books 컬렉션을 사용하여 간단한 도서관 관리 시스템을 구축합니다. 실습을 통해 문서를 생성하고, 참조를 사용하여 연결하고, 컬렉션 간 관련 데이터를 쿼리하고, 이러한 참조를 업데이트하고, 인덱스를 사용하여 쿼리 성능을 개선하는 방법을 배우게 됩니다. 이 랩은 MongoDB 에서의 데이터 모델링에 대한 실질적인 기초를 제공합니다.
컬렉션 및 참조 문서 생성
이 단계에서는 데이터베이스를 설정하고 authors와 books라는 두 개의 컬렉션을 생성합니다. 책을 저자에게 연결하여 문서 참조의 기본 개념을 배우게 됩니다.
먼저 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 를 사용하여 확장 가능하고 효율적인 애플리케이션을 구축하는 데 필수적입니다.

