정환타 개발노트

MongoDB Data Model(Relationship, Tree 구조) 본문

Dev-Database/NoSQL

MongoDB Data Model(Relationship, Tree 구조)

JungHwanTa 2020. 2. 5. 12:03

Data Model

이번에는 데이터 모델들에 대해 기술을 하겠다.

MongoDB 또한 documents간의 관계를 설정할 수 있는데, 그러한 관계를 설정할 수 있는 모델 구조는 다음과 같다.

 

One-to-One Relationship(일대일 관계)

두개의 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"
            }
}

위와 같은 데이터 모델을 사용하면, 하나의 쿼리를 통해 후원자의 전체 정보를 검색할 수 있다.

 

One-to-Many Relationship(일대다 관계)

이번에는 위의 예제를 활용하여 일대다 관계를 가지는 데이터 모델이다.

아래에는 후원자가 여러 주소를 가지는 관계이며, 이번에도 주소'들'은 후원자들에 대해 reference를 가지고 있다.

{
   _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"
}

이번에도 만약 이름을 이용한 주소 조회가 많아 진다면, 한번의 쿼리를 통해서 데이터를 가져올 수는 없다.
따라서 MongoDB에서 사용하는 일대다 모델에서도 주소를 후원자에게 내장시키는 구조를 사용한다.

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

 

One-to-Many Relationship with Document References(문서 참조 일대다 관계)

위의 예시와 다르게, 일대다 관계에서 다(Many)의 수가 무한히 증가할 수 있거나(대량이거나), 데이터가 커질 수 있는 경우에는 document안에 저장하는 것 보다는 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"

}

 

Model Tree Structures(모델 트리 구조)

MongoDB에서 대규모 혹은 계층적 구조를 위한 데이터 관계를 모델링 할 수 있다.

일반적인 모델 트리 구조는 다음과 같다.

출처 : https://docs.mongodb.com/manual/applications/data-models-tree-structures/

또한 트리 구조는 다양한 방식의 구조을 가진다.

 

Model Tree Structures with Parent References - 부모(상위) 참조가 있는 트리 구조

Model Tree Structures with Child References - 자식(하위) 참조가 있는 트리 구조

Model Tree Structures with an Array of Ancestors - 조상 배열형태의 트리 구조

Model Tree Structures with Materialized Paths - 구체적인 경로를 가진 트리 구조

Model Tree Structures with Nested Sets - 중첩셋을 가진 트리구조

 

Model Tree Structures with Parent References 

먼저 부모 참조가 있는 트리 구조는 위의 그림에서 데이터를 부모(parent)를 명시하여 insert하는 구조이다.

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

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

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

 

Model Tree Structures with Child References     

다음은 자식 참조를 하는 트리구조이다. 위의 부모 참조와 비슷하게 데이터를 자식을 참조하여 insert한다. 자식은 여러개를 가질 수 있다.

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

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

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

위의 부모 참조와 자식 참조들은 부모와 자식필드에 인덱스를 지정하여 빠른 검색을 할 수도 있다.

db.categories.createIndex( { parent: 1 } )
db.categories.createIndex( { children: 1 } )

 

Model Tree Structures with an Array of Ancestors 

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

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

위를 보면 제일 하위노드인 'MongoDB'는 ancestors에 상위 조상들을 포함하지만 최상위인 'Books'는 ancestors를 가지지 않는다.

이러한 구조에서는 각 노드의 조상들을 조회할 수 있으며, 조상을 통해 노드를 찾을 수도 있다.

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

 

Model Tree Structures with Materialized Paths  

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

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

이 구조에서는 여러가지 조회형태를 가질 수 있다.

먼저 전체 트리를 검색하여 정렬하여 조회할 수 있다.

db.categories.find().sort( { path: 1 } )

또한, 경로 값의 모든 자손을 구할 수 있다.

db.categories.find( { path: /,Programming,/ } )

또한, 최상위 수준의 모든 자손을 구할 수 있다.

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

 

Model Tree Structures with Nested Sets  

 

중첩셋은 전체 노드를 탐색하여 통과할 때와 돌아올 때 총 두번의 방문을 하고 그 순서를 명시한다. 따라서 parent 필드와 첫 방문할 때의 순서인 left 필드, 돌아올 때 방문하는 순서인 right 필드 세가지를 참조한다.

db.categories.insert( { _id: "Books", parent: 0, left: 1, right: 12 } )
db.categories.insert( { _id: "Programming", parent: "Books", left: 2, right: 11 } )
db.categories.insert( { _id: "Languages", parent: "Programming", left: 3, right: 4 } )
db.categories.insert( { _id: "Databases", parent: "Programming", left: 5, right: 10 } )
db.categories.insert( { _id: "MongoDB", parent: "Databases", left: 6, right: 7 } )
db.categories.insert( { _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 } } );

위의 left, right는 규칙이 존재한다.

먼저, 자식인 노드들은 부모보다 right 값이 작으며 left 값은 크다.

따라서, 부모가 아닌 노드는 right 값이 자식보다 작다.

 

이러한 규칙을 적용해서 부모자식 탐색을 다른 노드보다 빨리할 수 있지만, 수정과정이 매우 어렵다는 단점이 있다.

Comments