몽고 db 9장

9.1 스키마 설계 고려 사항

  • 스키마를 모델링하기 전에 먼저 쿼리 및 데이터 접근 패턴을 이해해야 한다.
  • 제약사항
    • 디비와 하드웨어 제약 사항을 이해해야 한다.
    • 도큐먼트 최대 크기는 16메가바이트이다.
    • 갱신 : 일부 도큐먼트 업데이트 전체 업데이트
    • 원자성 갱신 : 전체가 갱신될 수 있는지/아닌지
  • 쿼리 및 쓰기의 접근 패턴
    • 쿼리를 식별한 후에는 쿼리 수를 최소화해야 한다.
    • 함께 쿼리되는 데이터가 동일한 도큐먼트에 저장되도록 설계를 확인
    • 자주 사용하지 않은 데이터는 다른 컬렉션으로 이동
    • 동적데이터와 정적데이터 분리할 수 있도록 고려
      • 변경될 가능성이 있는 데이터(동적 데이터)와 변경되지 않을 데이터(정적 데이터)를 분리하여 저장 및 관리
  • 관계 유형
    • 관계가 변경될 때 갱신되는 도큐먼트 개수를 확인해야 한다.
    • 데이터가 쿼리하기 쉬운 구조인지 확인할 필요가 있다.
  • 카디널리티
    • 도큐먼트와 데이터가 어떻게 관련이 있는지 확인한 후에 관계의 카디널리티를 고려해야 한다.
    • 높은 카디널리티는 값이 매우 다양하다는 것을 의미하며, 낮은 카디널리티는 많은 중복 값을 가지고 있다는 것을 의미
    • 엔티티 간의 관계에서 카디널리티는 한 엔티티 인스턴스가 다른 엔티티와 얼마나 많은 관계
      • 일대일
      • 일대다
      • 다대다
    • 해당 데이터 필드에 대한 읽기 갱신 비율도 고려해야한다.

9.1.1 스키마 설계 패턴

  • 설계 패턴 종류
  • 다형성 패턴
    • 컬렉션 내 모든 도큐먼트가 유사하지만 동일하지 않은 구조를 가질 때 적합하다.
    • 공통 쿼리를 지원하는 도큐먼트에서 공통 필드를 식별하는 것이 포함
    • MongoDB가 스키마리스(schema-less)로, 동일한 컬렉션에서 서로 다른 형태의 도큐먼트를 저장할 수 있기 때문에 가능하다.
    {
    "_id": 1,
    "type": "dog",
    "name": "Fido",
    "breed": "Labrador", //추가
    "age": 7
    },
    {
    "_id": 2,
    "type": "cat",
    "name": "Whiskers",
    "lives": 9 //추가
    },
    {
    "_id": 3,
    "type": "fish",
    "name": "Goldie",
    "waterType": "Freshwater" //추가
    }
  • 속성 패턴
    • 속성의 이름과 값을 함께 쿼리할 수 있다.
    • 새로운 속성을 추가하거나 기존 속성을 제거하는 데 유연성을 제공한다.
    • 동적으로 변하는 속성이 있는 데이터를 모델링하는 데 적합하다.
    예를 들어, 다양한 상품에 대한 정보를 저장하는 상황에서 각각의 상품은 고유한 속성을 가질 수 있. 이런 경우, 속성 패턴을 사용하면 유용하다.각 상품 도큐먼트는 공통적으로 product_name 필드를 가지고 있지만, attributes 필드는 상품마다 다른 속성들을 포함하고 있다. 이런 방식으로 속성 패턴을 활용하면, 각 상품의 고유한 속성들을 유연하게 표현할 수 있다.정규화 - 컬렉션 간의 참조를 이용해 데이터를 여러 컬렉션으로 나눈다.정규화는 쓰기를 빠르게 만들고 비정규화는 읽기를 빠르게 만들 수 있다.정규화 단점
    • 쿼리 복잡성 증가
      • 데이터가 여러 테이블 분산
    • 성능 저하
      • 테이블 간의 조인은 데이터베이스 성능 저하
    • 개발 및 유지보수의 어려움
      • 정규화된 데이터베이스는 설계가 복잡
    비정규화 단점
    • 데이터 중복
      • 데이터의 중복 발생, 데이터 일관성 무결성 보장이 어려움
    • 데이터 불일치 위험
      • 중복된 데이터가 여러 위치에 저장될 때 한 곳에 데이터가 변경되었을 때의 다은 모든 위치를 업데이트하지 않으면 데이터 불일치 발생
    • 저장 공간의 비효율성
      • 중복 데이터는 추가적인 저장 공간을 사용
    • 업데이트 성능 저하
      • 중복 데이터 유지하기 위해 추가적인 업데이트 작업 필요할 수 있다.
    9.2.1 데이터 표현 예제
    • students
    • classes
    • studentsClasses
    그래서 학생 도큐먼트에 과목에 대한 참조를 내장해서 역참조 쿼리를 작성한다.classes 필드는 John Doe 가 수강하는 과목의 “_id” 배열을 보관한다.classes 에 저장된 id값을 사용하여 쿼리를 조회하면 된다.그리고 classes 필드에 내장 도큐먼트로 저장해 하나의 쿼리로 모든 정보를 가지고 온다.그런데 여기서 class로 Physics 클래스가 하나 더 있으면 갱신 시 두개가 한꺼번에 변경된다.마지막으로 사용할것은 내장과 참조가 혼합된 확장 참조 패턴을 사용한다. 자주 사용하는 정보로 서브도큐먼트를 생성하고 추가적인 정보는 실제 도큐먼트를 참조한다.첫번째 방식 vs 두번째 방식
    • 첫번째 방식의 장점
      • 관련 데이터가 하나의 문서에 내장 → 쿼리 성능 향상 (네트워크 비용을 줄인다.)
      • 관련 데이터가 하나에 모여있어서 애플리케이션 로직 단순화
    • 첫번째 방식의 단점
      • 여러 문서에서 같은 클래스 정보를 반복해서 사용하면 모든 문서를 찾아 업데이트 해야함
      • 문서 크기 증가 MongoDB 문서에 제한이 있어서 큰 데이터 세트에서는 문제가 될 수 있음
    • 두번째 방식의 장점
      • 클래스 정보를 중앙화, 정규화할 수 있음, 데이터 중복을 줄일 수 있다.
      • 새로운 클래스 추가되거나 기존 클래스 정보 변경하면 참조를 사용하여 중복 데이터 없이 변경을 관리 가능
    • 두번째 방식의 단점
      • 조인이 필요. 조인 연산은 $lookup 집계 연산자를 통해 지원 관계형 데이터베이스 보다 복잡 할 수 있다. 대량의 데이터 처리 시 성능 저하 초래
      • 각 클래스에 대한 정보 검색하기 위해 추가적인 쿼리가 필요할 수 있음
    • 정규화하기 좋을 때는 정보가 정기적으로 갱신되어야 할 때 정규화하는것이 좋다.
    • 정보를 생성할수록 더 적은 정보를 내장하고 내장된 필드의 내용이나 개수가 제한 없이 늘어나야 하면 일반적으로 그 정보는 내장되지 않고 참조되어야 한다.
    • 자체적인 도큐먼트로 저장되어야 한다.
  • { "_id": ObjectId("5123aasdbss123"), "name": "John Doe", "classes": [ { "_id": ObjectId("5123123ace4123213"), "class": "Trigonometry" }, { "_id": ObjectId("5123asda41231234"), "class": "Physics" }, { "_id": ObjectId("512312easd912381"), "class": "Women in Literature" }, { "_id": ObjectId("5123123f0d1231231"), "class": "AP European History" } ] }
  • { "_id": ObjectId("51231aaaa1231292"), "name": "John Doe", "classes": [ { "class": "Trigonometry", "credits": 3, "room": "204", }, { "class": "Physics", "credits": 3, "room": "159", }, { "class": "Women in Literature" "credits": 3, "room": "14b", } ] }
  • { "_id": ObjectId("51231aaaa1231292"), //불러옴 "name": "John Doe", "classes": [ { "class": "Trigonometry", "credits": 3, "room": "204", }, { "class": "Physics", "credits": 3, "room": "159", }, { "class": "Women in Literature" "credits": 3, "room": "14b", } ] }
  • classes 에 내장되어 있는 ObjectId
  • 과목에 대한 정보를 조회하려면 classes 컬렉션에 “_id”로 쿼리를 조회하는데 쿼리에 대한 조회가
  • { "_id": ObjectId("51123aasdbdd12312"), "name": "John Doe", "classes": [ ObjectId("512312aaaaa12391"), ObjectId("51231aaaa1231292"), //얘로 참조 ObjectId("51231aaabdfbfd11"), ] }
  • 애플리케이션에 적합한 상황에 따라 고려해서 사용한다.
  • 비정규화 - 모든 데이터를 하나의 도큐먼트에 내장한다.
  • 9.2 정규화 vs 비정규화
  • { "_id": 1, "product_name": "Awesome T-Shirt", "attributes": [ {"color": "blue"}, {"size": "medium"}, {"fabric": "cotton"} //차이 ] } { "_id": 2, "product_name": "Leather Jacket", "attributes": [ {"color": "black"}, {"size": "large"}, {"material": "leather"}, //차이2 {"style": "biker"} //차이3 ] }

내장 방식 참조 방식

작은 서브도큐먼트 자주 변하는 데이터  
일관성이 허용 즉각적 일관성 필요  
증가량이 적은 도큐먼트 증가량이 많은 도큐먼트  
두 번째 쿼리를 수행하는데 자주 필요한 데이터 결과에서 자주 제외되는 데이터  
빠른 읽기 빠른 쓰기  

예시

  • user 컬렉션
  • 계정 설정: 해당 사용자 도큐먼트에 관련 있다.
  • 최근 활동: 최근 활동의 증가량과 변화량에 따라 다르다. 크기가 고정된 필드라면 내장하는것이 유용
  • 친구: 내장X, 완전히 내장하지 말아야 한다.
  • 사용자가 생성한 모든 내용: 내장하지 않는다.
{
"_id": "unique_user_id", // 고유한 사용자 ID
"username": "user_name", // 사용자 이름
"email": "user_email", // 사용자 이메일
"account_settings": { // 계정 설정에 관련된 정보
"theme": "dark", // 예: 테마 설정
"language": "English", // 예: 언어 설정
// 추가 설정 필드...
},
"recent_activity": [ // 최근 활동 정보. 고정 크기를 가질 수 있도록 FIFO 방식으로 관리
{
"timestamp": "2023-06-20T12:30:00Z",
"activity": "Posted a comment"
},
// ...
// 최근 활동이 추가되면 가장 오래된 활동을 제거하여 크기를 유지
],
"friends": [ // 친구 목록. 다른 사용자 도큐먼트의 ID를 참조
"friend_user_id1",
"friend_user_id2",
// ...
],
"content_ids": [ // 사용자가 생성한 모든 콘텐츠. 별도의 컬렉션에 저장되는 콘텐츠의 ID를 참조
"content_id1",
"content_id2",
// ...
]
}

9.2.2 카디널리티

  • 카티널리티는 컬렉션이 다른 컬렉션을 얼마나 참조하고 있는지 보는것이다.
  • 일대일 혹은 다대다
  • 블로그 애플리케이션이 있다고 가정하면
    • 게시물마다 제목이 있는 경우 일대일 관계
    • 작성자는 여러 개의 게시글을 가지게 될 때 일대다 관계
    • 작성자는 여러 개의 게시물을 가지게 되므로 일대다 관계
    • 게시글은 여러 태그를 갖고 태그는 여러 게시물을 참조하고 있으니 다대다 관계
  • 몽고 디비에서는 대 소 관계를 사용하는데
    • 작성자가 게시물을 조금만 작성하면 작성한 게시물은 일대소 관계 (one-to-few)
    • 태그보다 게시물이 더 많으면 블로그 게시물과 태그는 다대소 관계 (many-to-few)
    • 게시물마다 댓글이 많이 달려 있으면 블로그 게시물과 댓글은 일대다 관계
  • 여기서 중요한것은 ‘적음’ 관계는 → 내장
  • ‘많음’ 관계는 → 참조 관계가 적당하다.

9.2.3 친구, 팔로워 그리고 불편한 관계

  • 구독을 구현하는 전형적인 방법에 대한 소개
  1. 게시자(producer)를 구독자(subscriber)의 도큐먼트에 넣는 방법
{
"_id": ObjectId("51250a5cd6512131f"),
"username": "batman",
"email": "batman@aynetechg.com",
"following": [
ObjectId("51250a7261239123123"),
ObjectId("51250a7affbdfa12312"),
]
}

사용자 도큐먼트가 존재하면 다음으로 쿼리해서 사용자가 관심을 가질 수 있게 활동을 모두 찾을 수 있다.

db.activities.find({"user": {"$in":
user["following"]}})

새로 게시된 활동에 관심 있는 사람을 모두 찾으려면 모든 사용자에 걸쳐 “following” 필드를 쿼리해야 하지만, 역으로 followers 필드를 추가할 수 있다.

뭔가 작업을 할 때 알림을 보내야 한다면 모든 사용자를 바로 확인할 수 있다. 팔로우하는 사람을 모두 찾으려면 users 컬렉션 전체를 쿼리해야 한다.

{
"_id": ObjectId("51250a7affbdfa12312"),
"username": "joker",
"email": "joker@mailnator.com",
"followers": [
ObjectId("5123bdfb912381"),
ObjectId("51250vdvsnj112")
]
}

단순히 user 컬렉션을 위와 같이 만든다면 users 컬렉션 전체를 쿼리해야 하는 단점과 user 도큐먼트를 더욱 크고 자주 변경되도록 만들어야 한다. following, followers 모두 반환될 필요도 없는 컬럼이다.

그래서 구독에 대한 부분만 다른 컬렉션에(팔로우 대상의 id를 가진 컬렉션에) 저장함으로써 문제를 해결한다.

'IT' 카테고리의 다른 글

resilience 4j 발표 내용 정리  (0) 2024.04.21
몽고디비 11장 - 복제 셋 구성요소  (0) 2024.04.14
Mongo 7 - 집계  (0) 2024.03.11
6장 - 키-값 저장소 설계  (0) 2024.02.12
kafka 학습 내용 정리 - 추가중 ...  (0) 2024.02.10