MongoDB 스키마 디자인 (concise guide)

Raymond Yoo·2023년 1월 8일
1

MongoDB 잘 사용하기

목록 보기
1/1

MongoDB 데이터베이스를 사용할때
엔티티 간의 관계에 따라서 어떤식으로 스키마를 구성하면 좋을지 직관적으로 따르기 좋은 가이드를 정리해본다.

MongoDB 스키마 디자인에서 첫번째 원칙은 다음과 같다.

mongodb 에서는 되도록 embedded document 로 데이터를 구성하는 것이 바람직하다.

One-to-One

{
  _id: ObjectId("AAA"),
  name: "Joe Karlsson",
  identity: {
    idNumber: "1123519",
    registeredAt: "2023-01-01 09:30:00",
  },
}

위에서 언급한 Rule 1 에 따라서 embedded document 방식으로 구성할 수 있으면 embedded document 를 사용하는 것이 좋다.

One-to-Few

{
  _id: ObjectId("AAA"),
  name: "Joe Karlsson",
  identity: {
    idNumber: "1123519",
    registeredAt: "2023-01-01 09:30:00",
  },
  company: "MongoDB",
  twitter: "@JoeKarsson1",
  twitch: "joe_karlsson",
  website: "joekarlsson.com",
  addresses: [
    { street: "123 Sesame St", city: "Anytown", cc: "USA" },
    { street: "123 Avenue Q", city: "New York", cc: "USA" },
  ]
}

어떤 사용자가 여러 개의 집을 갖고 있는 경우 또는 집과 사무실을 모두 자신의 주소로 등록하는 경우 주소 데이터는 Array 형태가 된다.
위에서 언급한 Rule 1 에 따라서 embedded document 방식으로 구성할 수 있으면 embedded document 를 사용하는 것이 좋다.

mongodb 에서 어떤 데이터를를 embedded document 로 구성하려고 했는데
나중에 알고보니 그 embedded document 를 감싸고 있는 부모 도큐멘트 없이 사용할 필요가 생긴다면
해당 데이터는 별로의 스키마로 구성하는 것이 바람직하다.

One-to-Many

{
  _id: ObjectId("123"),
  name: "steering wheel",
  manufacturer: "Acme Corp",
  catalog_number: 1234,
  parts: [
    ObjectId("BBB"),
    ObjectId("CCC"),
    ObjectId("DDD")
  ]
}
[
  {
    _id: ObjectId("BBB"),
    partNo: "123-ABC-456",
    name: "#1 steering wheel handle right",
    qunatity: 94,
    cost: 0.54,
    price: 2.99
  },
  {
    _id: ObjectId("CCC"),
    partNo: "777-def-234",
    name: "#2 steering wheel handle left",
    qunatity: 102,
    cost: 0.54,
    price: 2.99
  },
  {
    _id: ObjectId("DDD"),
    partNo: "654-ein-19",
    name: "#3 steering wheel handle middle",
    qunatity: 99,
    cost: 0.33,
    price: 2.78
  }
]

아래쪽의 도큐멘트는 개별 부품을 의미하고
위쪽의 도큐멘트는 여러개의 부품을 조합해서 만든 하나의 기계 파트를 의미한다.

제한된 개수의 어떤 대상과 One-To-Many 관계를 맺는 경우가 있다면 도큐멘트 id 를 참조해서 embedded array 방식으로 다루면 된다.

mongodb 에서 JOIN 이나 $lookup 실행에 비용이 많이 든다는 것은 사실이지만
별도의 스키마로 구성하는 것이 디자인 상으로 더 바람직한 설계라는 판단이 선다면 별도의 스키마로 구성하는 것을 망설일 필요는 없다.

One-to-Squillions

{
  _id: ObjectId("RRR"),
  name: "goofy.example.com",
  ipAddr: "127.66.66.66"
}
[
  {
    _id: ObjectId("abcd1234"),
    time: ISODate("2023-03-28T11:30:41.382Z"),
    message: "The CPU is on fie!!!",
    host: ObjectId("RRR")
  },
  {
    _id: ObjectId("abcd5678"),
    time: ISODate("2023-04-01T16:50:22.177Z"),
    message: "Drive is hosed",
    host: ObjectId("RRR")
  },
  ...
]

위의 예시는 로그 데이터이고 로그 데이터는 일반적으로 무한히 증가한다.
이런 경우에는 참조의 방향을 뒤집어서 squillion 쪽의 개별 도큐멘트가 one 쪽의 도큐멘트를 참조하도록 구성하면 좋다.

mongodb 를 사용할때 무조건 기억해야 하는 제약사항은 개별 도큐멘트의 최대 크기는 16MB 라는 사실이다.
그러므로 Array 가 무한히 늘어날 것 같다면 무조건 여기와 비슷한 방식으로 구성해야 한다.

Many-to-Many

[
  {
    _id: ObjectId("AAF11"),
    name: "Joe Karlsson",
    tasks: [
      ObjectId("FAD9"),
      ObjectId("FC02"),
      ObjectId("ZP17")
    ]
  },
  {
    _id: ObjectId("AAE23"),
    name: "Joe Karlsson",
    tasks: [
      ObjectId("FAD9"),
      ObjectId("FC02"),
      ObjectId("ZP17")
    ]
  }
]
[
  {
    _id: ObjectId("FAD9"),
    description: "Learn MongoDB",
    dueDate: ISODate("2023-01-07T10:00:00.915Z"),
    owners: [
      ObjectId("AAF11"),
      ObjectId("AAE23"),
    ]
  },
  {
    _id: ObjectId("FC02"),
    description: "Learn Mongoose",
    dueDate: ISODate("2023-02-22T18:00:00.306Z"),
    owners: [
      ObjectId("AAF11"),
    ]
  },
]

사실 이 다섯번째 원칙이 가장 중요하다고 할 수도 있는데
NoSQL 이 등장한 이유중의 하나는 데이터를 어떻게 구성하고 저장하는지 보다는 애플리케이션 로직에 집중해서 애플리케이션 로직을 보다 효율적으로 구현하자는 의도였다.

Outlier Pattern

위에서 언급한 One-to-Squillions 관계를 다루는 다른 방식이 있다.

[
  {
    _id: ObjectId("KimKardashian9876"),
    displayName: "Kim Kardashian West",
    followers_count: "64500485",
    followers: [
      ObjectId("KrysJenner"),
      ObjectId("CaitlynJenner"),
      ObjectId("JustinBeaver"),
      ...
    ],
    hasExtras: true
  },
  {
    _id: ObjectId("KimKardashian79102"),
    origin: ObjectId("KimKardashian9876"),
    isOverflow: true,
    followers: [
      ObjectId("KanyeWest"),
      ObjectId("EllenPage"),
      ObjectId("AnneHathaway"),
      ...
    ],
    hasExtras: true
  },
]

이런식으로 embedded array 처럼 다루지만
추가 데이터가 있다는 것을 hasExtras 를 사용해서 표시하고 이전 데이터에서 이어진다는 것을 isOverflow 를 사용해서 표시하는 방법이 있다.

이와 같은 독특한 여러가지 패턴은 MongoDB Schema Design Pattern 이라는 이름으로 정리되어 있다.
다음 링크에서 확인할 수 있다.
Building with Patterns: A Summary


출처

[1] MongoDB University - M320: Introduction to MongoDB Data Modeling: https://learn.mongodb.com/courses/introduction-to-mongodb-data-modeling
[2] How To Design a Document Schema in MongoDB: https://www.digitalocean.com/community/tutorials/how-to-design-a-document-schema-in-mongodb
[3] Building With Patterns: The Outlier Pattern: https://www.mongodb.com/blog/post/building-with-patterns-the-outlier-pattern
[4] MongoDB Schema Design Best Practices: https://youtu.be/leNCfU5SYR8
[5] Data Modeling with MongoDB: https://youtu.be/3GHZd0zv170
[6] A Complete Methodology of Data Modeling for MongoDB: https://youtu.be/DUCvYbcgGsQ
[7] Advanced Schema Design Patterns: https://youtu.be/bxw1AkH2aM4

profile
세상에 도움이 되고, 동료에게 도움이 되고, 나에게 도움이 되는 코드를 만들고 싶습니다.

2개의 댓글

comment-user-thumbnail
2023년 1월 12일

와.... 요즘 쉬시는 척 하시더니 또또 공부... 역시 진짜 후.....

1개의 답글