쿼카러버의 기술 블로그
[MongoDB] 관계 구조 별 스키마 디자인하는법 (간단 요약) (1:1, 1:N, N:N) 본문
[MongoDB] 관계 구조 별 스키마 디자인하는법 (간단 요약) (1:1, 1:N, N:N)
quokkalover 2022. 2. 26. 15:17
몽고디비의 특징은, application specific한 schema를 짤 수 있다는 점이다.
정해진 룰이 없기 때문에, 내 어플리케이션이 어떻게 데이터를 쿼리할 것인지에 대해서만 고민하면 된다. 이 말은 즉, 특정 스키마가 어떤 어플리케이션에선 효율적이지만 또 어떤 어플리케이션에서는 비효율적이 될 수 있음을 의미한다.
본 글에서는 Mongo DB에 잘 정리된 글이 있어, 해당 문서에서 제안하는 다음의 룰을 기본으로, 스키마를 어떻게 짜면 좋을지에 대해 매우 간단한 초 예약본이다. 그냥 내가 나중에 보려고 작성한 부분이라 자세한 설명들은 생략돼있다. 사실 이거만 읽고, 예제만 보더라도 DB에 대한 개념이 있는 독자라면 바로 이해할 것이라 생각한다.
참고로 자세한 내용은 Mongo DB문서를 직접 읽어보길 바란다. 또, Embed구조의 장점이나 Referencing의 장점 등의 내용은 나중에 다른 포스트에서 별도로 다루도록 하겠다.
자 그럼 간단한 설명과 예제들을 살펴보자.
1:1 (one to one)
embed!
아래 예시와 같이 모든 1:1 data를 key-value pair로 저장한다.
아래와 같이 이름을 두개를 가지는 경우는 없으니까 아래처럼 활용할 수 있겠다.
{
"_id": "ObjectId('AAA')",
"name": "Joe Karlsson",
"company": "MongoDB",
"twitter": "@JoeKarlsson1",
"twitch": "joe_karlsson",
"tiktok": "joekarlsson",
"website": "joekarlsson.com"
}
1:FN (one to few)
embed!
한 유저가 살고있는 주소가 엄청 많지는 않겠지만, 어쨋든 복수로 있을 수 있는 경우에는 그냥 embed한다.
{
"_id": "ObjectId('AAA')",
"name": "Joe Karlsson",
"company": "MongoDB",
"twitter": "@JoeKarlsson1",
"twitch": "joe_karlsson",
"tiktok": "joekarlsson",
"website": "joekarlsson.com",
"addresses": [
{ "street": "123 Sesame St", "city": "Anytown", "cc": "USA" },
{ "street": "123 Avenue Q", "city": "New York", "cc": "USA" }
]
}
1:MN (one to many)
embed! (with reference)
특정 제품에 대한 상세 정보를 제공해준다고 생각해보자. 예를들어 자전거만 하더라도, 아래 그림처럼 다양한 부품들이 있고, 각 부품들의 상세정보를 적어줘야할 때는 어떻게 해야 할까?
일단 각 부품들만 따로따로 상세정보를 볼 수 있어야 하기 때문에, 콜렉션을 나누어주는게 좋다. 그리고, 그리고 Product에서는 각 Parts들에 대한 Object-id를 담은 객체 배열을 embed한다.
Products
{
"name": "left-handed smoke shifter",
"manufacturer": "Acme Corp",
"catalog_number": "1234",
"parts": ["ObjectID('AAAA')", "ObjectID('BBBB')", "ObjectID('CCCC')"]
}
Parts
{
"_id" : "ObjectID('AAAA')",
"partno" : "123-aff-456",
"name" : "#4 grommet",
"qty": "94",
"cost": "0.94",
"price":" 3.99"
}
1:SN (one to squillions)
reference!
만약 몇백만개의 subdocument가 있는 경우에는 어떻게 할까? (real world에서는 이런 경우가 허다하다) 예를 들어 log를 담아야 하는 경우가 대표적인 예가 될 수 있다.
MongoDB는 한 document당 16MB의 용량제한이 있기 때문에 무제한으로 배열에 내용을 담는 것은 매우 위험하다. Object ID만 넣어주더라도 마찬가지다.
그래서 이럴대는 관계구조를 양쪽에 다 가져가려고 하지말고, Log를 예로 들면 Log Message만 Host에 대한 정보를 담아두고, Host 컬렉션에는 그냥 Host에 대한 정보만 담아두도록 한다.
Hosts:
{
"_id": ObjectID("AAAB"),
"name": "goofy.example.com",
"ipaddr": "127.66.66.66"
}
Log Message:
{
"time": ISODate("2014-03-28T09:42:41.382Z"),
"message": "cpu is on fire!",
"host": ObjectID("AAAB")
}
N:N : (Many to Many)
reference!
채팅방이나 todo-app의 task와 관련된 스키마를 짠다고 가정해보자.
예를 들어 어떤 유저가 A라는 task를 담고 있고, task에는 A라는 유저가 task를 가지고 있다고 데이터를 저장하는 경우를 생각해보자. user도 여러 task를 가져가고 task도 여러 user를 가져간다.
쉽게 말하면 이럴 때는 user도 task의 objectID를 가진 subarray를 embed하고 task 스키마도 user의 objectID를 가진 subarray를 embed해야 한다.
1:N (user:tasks) N:1(user:task)의 구조를 모두 가져가지는 아래 예시를 보자.
Users:
{
"_id": ObjectID("AAF1"),
"name": "Kate Monster",
"tasks": [ObjectID("ADF9"), ObjectID("AE02"), ObjectID("AE73")]
}
Tasks:
{
"_id": ObjectID("ADF9"),
"description": "Write blog post about MongoDB schema design",
"due_date": ISODate("2014-04-01"),
"owners": [ObjectID("AAF1"), ObjectID("BB3G")]
}
요약 :
Mongo DB 스키마 디자인의 기본 룰:
- Rule 1: 특별히 문제가 되지 않으면 embedding한다.
- Rule 2: 해당 객체만 조회하는 경우가 잦으면 Rule 1에서 말한 문제가 되는 케이스기 때문에 embedding하지 않는다.
- Rule 3: join이나 lookup은 왠만하면 피하되, 더 나은 스키마 디자인을 만들 수 있을때는 사용하자.
- Rule 4: 배열에 담아야하는 element가 많은 경우에는 embed하지 말고 reference하거나 reference배열을 embed하자. document당 사이즈 제한이 있기 때문이다. 그리고 몇전개의 document를 담은 배열을 넣고, object-id로 reference해야 하는 High-cardinality array의 경우에는 embed하지 말자.
- Rule 5: 특정 스키마가 좋다고 생각하지말고, 내 애플리케이션에 최적화된 스키마를 고민해서 모델링하자.
참고자료
https://www.mongodb.com/developer/article/mongodb-schema-design-best-practices/