[MongoDB] Data Model (Documents간의 관계 설정 구조)

2020. 12. 17. 15:42Database/MongoDB

1. Data Model

1) 일대일 관계

두 개의 Document에 대해 일대일 관계로 구성이 가능하다. 예를 들어 하단에는 후원자(patron)와 주소를 매핑하고 주소에는 후원자에 대한 reference(patron_id)가 포함된다.

{
  _id: "joe",
  name: "Joe Bookreader"
}

{
  patron_id: "joe",
  street: "123 Fake Street",
  city: "Faketon",
  state: "MA",
  zip: "12345"
}

만약 이 구조에서 이름(name)을 가지고 주소를 검색하는 일이 많아진다면 해당 데이터를 조회하기 위한 여러 쿼리를 샐행해야한다. 따라서 일대일 관계에서 더 나은 데이터 모델은 하단의 후원자 데이터에 주소가 내장되는 구조이다.

{
  _id: "joe",
  name: "Joe Bookreader",
  address: {
             street: "123 Fake Street",
             city: "Faketon",
             state: "MA",
             zip: "12345"
           }
}

2) 일대다 관계

일대다 관계에서의 예시로는 후원자가 여러 주소를 가지는 관계를 생각하면 쉽다.

{
  _id: "joe",
  name: "Joe Bookreader"
}

{
  patron_id: "joe",
  street: "123 Fake Street",
  city: "Faketon",
  state: "MA",
  zip: "12345"
}

{
  patron_id: "joe",
  street: "1 Some Other Street",
  city: "Boston",
  state: "MA",
  zip: "12345"
}

이 경우에도 이름을 이용한 주소 조회가 많아진다면 한번의 쿼리로 데이터를 가지고 올 수 없기 때문에 일대다 모델에서도 주소를 후원자에게 내장시키는 구조를 사용한다.

{
  _id: "joe",
  name: "Joe Bookreader",
  address: {
             street: "123 Fake Street",
             city: "Faketon",
             state: "MA",
             zip: "12345"
           },
           {
             street: "1 Some Other Street",
             city: "Boston",
             state: "MA",
             zip: "12345"
           }
}

3) 문서 참조 일대다 관계

일대다 관계에서 다의 수가 무한히 증가할 수 있거나 데이터가 커질 경우에는 Document를 참조하는 것이 좋다. 예를 들어, 출판사와 책의 일대다 관계를 생각하면 쉽다.

{
  title: "MongoDB: The Definitive Guide",
  author: ["Kristina Chodorow", "Mike Dirolf"],
  published_date: ISODate("2010-09-24"),
  pages: 216,
  language: "English",
  publisher: {
              name: "O'Reilly Media",
              founded: 1980,
              location: "CA"
             }
}

{
  title: "50 Tips and Tricks for MongoDB Developer",
  author: "Kristina Chodorow",
  published_date: ISODate("2011-05-06"),
  pages: 68,
  language: "English",
  publisher: {
              name: "O'Reilly Media",
              founded: 1980,
              location: "CA"
             }
}

책을 조회하고 필요할 경우 출판사에 대한 데이터를 조회할 경우 다음과 같은 모델이 좋다. 하지만 똑같은 데이터가 여러번 반복되어 비효율적으로 데이터가 저장되기에 다음과 같이 해결할 수 있다.

{
  name: "O'Reilly Media",
  founded: 1980,
  location: "CA",
  
  books: [123456789, 234567890, ...]
}

{
  _id: 123456789,
  title: "MongoDB: The Definitive Guide",
  author: ["Kristina Chodorow", "Mike Dirolf"],
  published_date: ISODate("2010-09-24"),
  pages: 216,
  language: "English"
}

{
  _id: 234567890,
  title: "50 Tips and Tricks for MongoDB Developer",
  author: "Kristina Chodorow",
  published_date: ISODate("2011-05-06"),
  pages: 68,
  language: "English"
}

책을 단독으로 조회할 수 있으며 출판사를 중복하여 저장할 필요가 없지만 데이터가 추가 될 때마다 위의 배열이 늘어날 수 있으며 , 책 이름을 통하여 출판사를 조회하기 복잡하기 때문에 최종적으로 다음과 같이 해결할 수 있다.

{
  _id: "oreilly"
  name: "O'Reilly Media",
  founded: 1980,
  location: "CA"
}

{
  _id: 123456789,
  title: "MongoDB: The Definitive Guide",
  author: ["Kristina Chodorow", "Mike Dirolf"],
  published_date: ISODate("2010-09-24"),
  pages: 216,
  language: "English",
  
  publisher_id: "oreilly"
}

{
  _id: 234567890,
  title: "50 Tips and Tricks for MongoDB Developer",
  author: "Kristina Chodorow",
  published_date: ISODate("2011-05-06"),
  pages: 68,
  language: "English",
  
  publisher_id: "oreilly"
}

 

 

 

2. Model Tree 구조

MongoDB에서 대규모 혹은 계층적 구조를 위한 데이터 관계를 모델링 할 수 있다. 또한 트리 구조는 다양한 방식의 구조를 가진다.

일반적인 Model Tree 구조

1) 부모(상위) 참조가 있는 트리 구조

부모 참조가 있는 트리 구조는 기본 Model Tree 그조에서 데이터를 부모를 명시하여 insert하는 구조이다.

db.categories.insertMany( [
   { _id: "MongoDB", parent: "Databases" },
   { _id: "dbm", parent: "Databases" },
   { _id: "Databases", parent: "Programming" },
   { _id: "Languages", parent: "Programming" },
   { _id: "Programming", parent: "Books" },
   { _id: "Books", parent: null }
] )

이를 통해 각 노드별로 부모를 구하거나 부모를 통해 노드를 구할 수 있다.

db.categories.findOne( { _id: "MongoDB" } ).parent
db.categories.find( { parent: "Databases" } )


// 인덱스를 이용한 빠른 검색
db.categories.createIndex( { parent: 1 } )

2) 자식(하위) 참조가 있는 트리 구조

위의 부모 참조와 비슷하게 데이터를 insert한다. 단, 자식은 여러 개를 가질 수 있다.

db.categories.insertMany( [
   { _id: "MongoDB", children: [] },
   { _id: "dbm", children: [] },
   { _id: "Databases", children: [ "MongoDB", "dbm" ] },
   { _id: "Languages", children: [] },
   { _id: "Programming", children: [ "Databases", "Languages" ] },
   { _id: "Books", children: [ "Programming" ] }
] )

자식 참조 또한 각 노드별 자식을 구하거나 자식을 통해 부모를 구할 수 있다.

db.categories.findOne( { _id: "Databases" } ).children
db.categories.find( { children: "MongoDB" } )

// 인덱스를 이용한 빠른 검색
db.categories.createIndex( { children: 1 } )

3) 조상 배열 형태의 트리 구조

조상 배열을 가지는 트리 모델은 각 노드에 ancestors 필드를 함께 명시한다. ancestors 필드는 list로 0번 인덱스 부터 상위 조상이 들어가며 각 단계별 조상을 지정한다. parent 필드에는 부모를 참조한다.

db.categories.insertMany( [
  { _id: "MongoDB", ancestors: [ "Books", "Programming", "Databases" ], parent: "Databases" },
  { _id: "dbm", ancestors: [ "Books", "Programming", "Databases" ], parent: "Databases" },
  { _id: "Databases", ancestors: [ "Books", "Programming" ], parent: "Programming" },
  { _id: "Languages", ancestors: [ "Books", "Programming" ], parent: "Programming" },
  { _id: "Programming", ancestors: [ "Books" ], parent: "Books" },
  { _id: "Books", ancestors: [ ], parent: null }
] )

가장 하위 노드인 'MongoDB'는 ancestors에 상위 조상들을 포함하지만 최상위 'Books'는 ancestors를 가지지 않는다. 이러한 구조에서는 각 노드의 조상들을 조회할 수 있으며, 조상을 통해 노드를 찾을 수 있다.

db.categories.findOne( { _id: "MongoDB" } ).ancestors
db.categories.find( { ancestors: "Programming" } )

// 인덱스를 이용한 빠른 검색
db.categories.createIndex( { ancestors: 1 } )

4) 구체적인 경로를 가진 트리 구조

구체적인 경로를 가진 트리 구조는 경로를 작성하여 참조하는 구조이다. path 필드에 각 노드별 경로를 작성하며 구분자로는 ','를 사용한다.

db.categories.insertMany( [
   { _id: "Books", path: null },
   { _id: "Programming", path: ",Books," },
   { _id: "Databases", path: ",Books,Programming," },
   { _id: "Languages", path: ",Books,Programming," },
   { _id: "MongoDB", path: ",Books,Programming,Databases," },
   { _id: "dbm", path: ",Books,Programming,Databases," }
] )

이 구조에서는 전체 트리를 검색하여 정렬하여 조회할 수 있으며, 경로 값의 모든 자손을 구할 수도 있다. 또한 최상위 수준의 모든 자손을 구할 수 있다.

db.categories.find().sort( { path: 1 } )
db.categories.find( { path: /,Programming,/ } )
db.categories.find( { path: /^,Books,/ } )

// 인덱스를 이용한 빠른 검색
db.categories.createIndex( { path: 1 } )

5) 중첩셋을 가진 트리 구조

중첩셋을 가진 트리 구조

중첩셋은 전체 노드를 탐색하여 통과할 때와 돌아올 때 총 두 번의 방문을 하고 그 순서를 명시한다. 따라서 parent 필드와 첫 방문할 때의 순서인 left 필드, 돌아올 때 방문하는 순서인 right 필드 세 가지를 참조한다. 기본적으로 자식인 노드들은 부모보다 right 값이 작으며 left 값은 크다. 따라서 부모가 아닌 노드는 right 값이 자식보다 작다.

db.categories.insertMany( [
   { _id: "Books", parent: 0, left: 1, right: 12 },
   { _id: "Programming", parent: "Books", left: 2, right: 11 },
   { _id: "Languages", parent: "Programming", left: 3, right: 4 },
   { _id: "Databases", parent: "Programming", left: 5, right: 10 },
   { _id: "MongoDB", parent: "Databases", left: 6, right: 7 },
   { _id: "dbm", parent: "Databases", left: 8, right: 9 }
] )

하위 항목을 검색하는 경우는 다음과 같이 사용할 수 있다. 이 방법은 부모자식 탐색을 다른 노드보다 빨리할 수 있지만, 수정 과정이 매우 어렵다.

var databaseCategory = db.categories.findOne( { _id: "Databases" } );
db.categories.find( { left: { $gt: databaseCategory.left }, right: { $lt: databaseCategory.right } } );

[참고] docs.mongodb.com/manual/core/data-modeling-introduction/

[참고] junghwanta.tistory.com/32?category=857330

728x90

'Database > MongoDB' 카테고리의 다른 글

[MongoDB] Database 참조  (0) 2020.12.18
[MongoDB] Model 활용  (0) 2020.12.18
[MongoDB] Text Search  (0) 2020.12.17
[MongoDB] Bulk Write, Retryable Write/Read  (0) 2020.12.16