본문 바로가기

DB/MongoDB

[MongoDB] 17. 데이터 모델링과 인덱스 - 데이터 모델링

728x90
반응형

데이터 모델링과 인덱싱

DB를 사용하는 환경에서 하드웨어 개선없이 성능을 개선하는 방법

데이터 모델링

  • 업무 수행 시 발생하는 데이터를 정확하고 효율적으로 DB에 저장하기 위해 데이터 구조를 설계하는 과정
  • MongoDB의 특성을 고려하여 저장할 데이터의 구조를 정하는 작업
DBMS RDBMS MongoDB
설계 방식 테이블 설계 후 컬럼을 설계함 도큐먼트 설계 후 컬렉션을 설계

애플리케이션의 처리방안을 고려한 도큐먼트 구조를 어떻게 설계하느냐에 따라 데이터 "정합성(일관성)"과 "성능"에 큰 영향을 주게 되므로 이에 대한 정확한 이해가 필요함.

 

모델링 예시 - 게시판

  • 각 게시판은 "이름"을 가진다.
  • 게시판에 글을 쓰는 사용자는 "이름, 생년월일, 포인트"를 가진다.
  • 각 게시글은 게시판에 작성 가능, "게시판 id, 사용자 id, 제목, 내용"을 가진다.
  • 각 게시글은 "게시글 id, 사용자 id"로 조회한다.
  • 각 게시글의 조회 수는 사용자 당 1회만 증가, 동일한 게시글을 여러번 읽어도 추가로 증가하지 않는다.
게시판 컬렉션과 컬렉션
컬렉션 사용자 게시판 게시글 조회
필드 이름
생년월일
포인트
이름 게시판 id
사용자 id
제목
내용
게시글 id
사용자 id
  • 게시판에 존재하는 관계 대응수
    • 게시판과 게시글 = 1:N
    • 사용자와 게시글의 조회  = N:M
    • 사용자와 포인트 = 1:1
  • MongoDB에서도 도큐먼트들 사이에 존재하는 관계(외래키)를 표현하는 방법이 필요함.

관계를 표현하기 위한 도큐먼트 구조

embedded 방식

  • 다른 객체를 도큐먼트 내부에 포함 시키는 방식
  • 장점
    • 원자성을 지키기 쉬움
    • 내용을 읽는 시간이 빠름
    • 도큐먼트가 여러 서버에 나뉘어져(샤딩) 있을 때 속도 차이가 커짐
    • 빈번하게 함께 읽게 되는 정보는 임베디드 방식을 사용하는 것이 좋음
  • 단점
    • 도큐먼트 사이즈 제한(16MB)이 있어 문제가 생김
    • 도큐먼트 사이즈가 크면 램에 불러들여 수정할 때 속도가 느려짐
    • 도큐먼트 사이즈는 100KB 이하로 유지하는 것을 추천
    • 데이터의 중복으로 인한 저장 공간이 많이 필요

예시1 - 1:N의 경우

db.modeling.insertOne({
   _id : "Pupbani",
   name : "Pupbani J",
   tel : [
      {kind : "main",tel_num : "010-1234-1234"},
      {kind : "sub1",tel_num : "010-1111-2121"},
      {kind : "sub2",tel_num : "010-2234-1234"},
   ]
})

예시2 - 도서 출판사 정보 저장

db.modeling.insertMany([
    {
        title : "Pupbani와 함께 몽고디비 배우기",
        author : ["Pupbani","Jake"],
        published_date : ISODate("2022-10-12"),
        pages : 250,
        language : "한글",
        publisher : {
           name : "IT 출판사",
           founded : 1990,
           location : "CA"
        }
    },
    {
        title : "Pupbani와 함께 DB 배우기",
        author : ["Pupbani","Jony"],
        published_date : ISODate("2022-12-12"),
        pages : 252,
        language : "영어",
        publisher : {
           name : "IT 출판사",
           founded : 1990,
           location : "CA"
        }
    }
])

동일한 출판사에 대하여, 정보는 계속 중복됨

 

reference 방식

  • 참조할 도큐먼트의 id를 저장하는 방식

예시 - 도서와 출판사

db.modeling.insertMany([
    {
       name : "IT 출판사", 
       founded : 1990, 
       location : "CA",
       books : [4106,8211]
    },
    {
        _id : 4106,
        title : "Pupbani와 함께 몽고디비 배우기",
        author : ["Pupbani","Jake"],
        published_date : ISODate("2022-10-12"),
        pages : 250,
        language : "한글",
    },
    {
        _id : 8211,
        title : "Pupbani와 함께 DB 배우기",
        author : ["Pupbani","Jony"],
        published_date : ISODate("2022-12-12"),
        pages : 252,
        language : "영어",
    }
])

출판사와 도서 정보를 분리하고 출판사에서 만들어진 도서는 배열에 저장.

도서의 _id를 사용하여 1:N의 관계를 표현.

 

(임베디드)내장 방식이 좋은 경우 (레퍼런스)참조 방식이 좋은
작은 서브 도큐먼트 큰 서브 도큐먼트
주기적으로 변하지 않는 데이터 자주 변하는 데이터
결과적인 일관성이 허용될 때 즉각적인 일관성이 필요할 때
증가량이 적은 도큐먼트 증가량이 많은 데이터
빠른 읽기 빠른 쓰기

 

애플리케이션 설계시 고려사항

  • 스키마 설계
  • 데이터 내장 방식과 참조 방식의 선택
  • 최적화 팁
  • 일관성 고려 사항
  • 스키마 마이그레이션
  • 스키마 관리 방

스키마 설계

데이터 표현의 핵심 요소 - 데이터가 도큐먼트에서 표현되는 방식인 "스키마의 설계"

애플리케이션에서 원하는 방식으로 데이터를 표현하는 것이 좋음

스키마 모델링 전에 쿼리, 데이터 접근 패턴을 이해해야함.

 

스키마 설계 시 고려사항

  • 제약사항
    • DB와 H/W 사이의 제약 사항의 이해 필요
    • MongoDB 도큐먼트의 최대 크기는 16MB
    • 갱신은 전체 도큐먼트를 다시 쓰며, 원자성 갱신은 도큐먼트 단위로 실행
  • 쿼리 및 쓰기의 접근 패턴
    • 쿼리가 실행되는 시기와 빈도 파악
    • 쿼리를 식별한 후에 쿼리 수를 최소화, 함께 쿼리되는 데이터가 동일한 도큐먼트에 저장되도록 설계
  • 관계 유형
    • 애플리케이션 요구 사항 측면과 도큐먼트 간 관계 측면에서 어떤 데이터가 관련되어 있는지 파악
    • 그런 다음 도큐먼트를 내장하거나 참조할 방법 결정
  • 카디널리티
    • 1:1, 1:N, N:M의 대응수를 고려
    • 해당 데이터를 읽기 갱신 비율도 생각해야함.

 

스키마 설계 패턴

  • 다형성 패턴
    • 컬렉션 내의 도큐먼트가 유사하지만 동일하지 않은 구조를 가질 때, 공통 필드를 식별하는 것이 필요함
  • 속성 패턴
    • 쿼리하려는 도큐먼트에 필드의 서브셋이 있는 경우, 쿼리하려는 필드가 도큐먼트 서브셋에만 있는 경우
    • 배열로 만들고 배열에 인덱스를 만듬
  • 버킷 패턴
    • 데이터가 일정 기간 동안 스트림으로 유입되는 시계열 데이터에 적합함
    • 특정 시간 범위의 데이터를 버킷화하여 효율적임
  • 이상치 패턴
    • 도큐먼트의 쿼리가 애플리케이션의 정상적인 패턴을 벗어날 때 사용
    • 도서나 영화의 인기도가 갑자기 올라가면 플래그를 설정하고 추가적인 도큐먼트 만듬
  • 계산된 패턴
    • 데이터를 자주 계산해야 할 때는 데이터 접근 패턴이 읽기 집약적일 때
    • 백그라운드에서 계산을 수행하고, 계산값에 대한 근사치를 제공
  • 서브셋 패턴
    • 장비의 램 용량을 초과하는 작업이 있을 때 사용
    • 자주 사용하는 데이터와 자주 사용하지 않는 데이터를 다른 컬렉션으로 분할 
  • 확장된 참조 패턴
    • 전자 상거래에서 주문시 자주 이용되는 필드는 고객의 이름과 주소
    • 데이터를 중복 시키지 않고 정보를 조합하여 쿼리를 만듬
  • 근사 패턴
    • 정확도가 반드시 필요하지 않은 상황에서 유리
    • 게시글의 카운트를 한 번 마다 갱신하지 않고 10번이나 100번마다 갱신 할 수 있음
  • 트리 패턴
    • 쿼리가 많고 구조적으로 계층적인 데이터가 있을 때 사용
    • "하드 드라이브"는 "기억장치"와 "전자장치" 카테고리에 속하고, 어느 쪽에서도 찾을 수 있어야 함

 

정규화 vs 비정규화

  • 정규화
    • 컬렉션간의 참조를 이용해 데이터를 여러 컬렉션으로 나누는(분해) 작업 
  • 비정규화
    • 모든 데이터를 하나의 도큐먼트에 내장하는 것 
  • 정규화와 비정규화를 결정하는 것은 쉬운 일이 아님
  • 정규화는 쓰기를 빠르게 만들고, 비정규화는 읽기를 빠르게 만듬

 

카디널리티

하나의 컬렉션이 다른 컬렉션을 얼마나 참조하는지 나타내는 개념

  • 1:1, 1:N, N:M

MongoDB를 사용할 때는 "다수"라는 개념을 "많음"과 "적음"이라는 하위 범위로 나누면 개념상 도움이 됨.

  • 일대소 관계 - 각 사용자가 게시물을 조금만 작성할 경우 게시자와 게시글의 관계
  • 다대소 관계 - 태그보다 게시물이 더 많을 경우 게시물과 태그의 관계

많고 적음을 결정하면 무엇을 내장하고 무엇을 참조할지 결정하는데 도움이 됨.

  • "적음"관계는 내장이 더 적합하고, "많음"관계는 참조가 더 적합함.

 

데이터 조작을 위한 최적화

애플리케이션 최적화를 하려면 읽가와 쓰기 성능을 분석해 어느쪽에서 병목 현상이 일어나는지 파악 필요

읽기 최적화 

  • 일반적으로 올바른 인덱스를 사용해 하나의 도큐먼트에서 가능한 한 많은 정보를 반환하는 것과 관련 있음.

쓰기 최적화

  • 보통 갖고 있는 인덱스 개수를 최소화, 갱신을 가능한 효율적으로 수행하는 것과 관련 있음.

빠른 읽기와 쓰기에 최적화된 스키마는 종종 trade-off가 발생 , 애플리케이션에서 어떤 것이 더 중요한지 결정해야 함.

어떤 애플리케이션에서 쓰기가 더 중요하지만 각 쓰기에 대해 읽기를 수천번 한다면 읽기를 먼저 최적화해야함.

 

오래된 데이터 제거

어떤 데이터는 짧은 시간 동안만 중요

오래된 데이터를 제거하는 3가지 방법

  • capped collection
// capped 컬렉션 생성
db.createCollection("cap_Col",{capped:true,size:100000})

// 컬렉션이 capped 컬렉션인지 확인
db.cap_Col.isCapped()

  • TTL(Time-To-Live) collection
// 지정시간 후에 삭제
db.ttl_Col.createIndex({"lastModifiedDate":1},{expireAfterSeconds:50})

// 도큐먼트 삽입
db.ttl_Col.insert({"test":"hello","lastModifiedDate":new Date()})

  • 주기적으로 컬렉션 삭제
db.ttl_Col.drop()

 

데이터베이스와 컬렉션 구상

  • 도큐먼트 형태에 대한 구상이 끝나면 어떤 컬렉션 또는 어떤 DB에 넣을지 결정해야 함.
  • 지침 - 스키마가 유사한 도큐먼트는 같은 컬렉션에 보관함
  • 컬렉션에서는 lock저장을 중요하게 고려해야 함
  • DB내 모든 항목이 비슷한 품질, 비슷한 접근 패턴, 비슷한 트래픽 수준을 갖는 것이 좋음
  • MongoDB 4.2 이전에는 DB를 여러 개 사용하면 몇 가지 제약이 있음
  • $merge 연산자를 통해 결과를 다른 DB나 다른 collection에 저장할 수 있음.

 

일관성 관리

애플리케이션의 "읽기"에 필요한 일관성이 어느 정도인지 파악해야 함.

MongoDB는 다양한 수준의 일관성을 제공함

실시간 거래를 수행하기 위해 최근 쓴 데이터에 즉각적으로 읽기를 수행할 필요 있음

readConcern

  • 읽을 데이터의 일관성격리 속성을 제어
  • 제공하는 수준(level)
    • local
      • 과반수에 기록되었음을 확인하지 않고 데이터 반환(읽어온 데이터가 롤백될 수 있음)
      • causally consistent session 또는 트랜잭션에서 사용
    • available
      • 과반수에 기록되었음을 확인하지 않고 반환(읽어온 데이터가 롤백될 수 있음)
      • causally consistent session 또는 트랜잭션에서 사용할 수 없음
      • 샤딩된 클러스터에서 가장 낮은 레이턴시를 제공
      • 샤드된 컬렉션에서 orphaned document를 반환할 수 있음
    • majority
      • 과반수에 기록된 데이터를 반환
      • 이를 충족하기 위해 각 레플리카 셋 멤버들이 메모리의 majority-commit point 반환해야 함 이로 인해 위의 두 설정에 비해 성능이 떨어짐.
      • causally consistent session 또는 트랜잭션에서 사용
      • PSA 아키텍처를 사용할 때 이 설정을 쓰지 않게 설정 가능(Change Streams, 트랜잭션, 샤디드 클러스터에 영향을 줄 수 있음)
    • linearizabe
      • 읽기를 시작하기 전에 완료된 쓰기에 대한 데이터만 반환함.
      • 쿼리가 결과를 반환하기 전에 레플리카 셋 전체에 결과가 전파되길 기다림.
      • 읽기 시작 후 레플리카 셋의 과반이 재시작되어도, 반환된 데이터는 유효(writeConcernMajorityJournalDefault을 false로 변경하면 아닐 수 있음)
      • causally consistent session 또는 트랜잭션에서 사용할 수 없음
      • 프라이머리 노드에만 설정할 수 있음
      • aggregate의 $out, $merge 스테이지에서 사용할 수 없음
      • 유니크하게 식별가능한 단일 도큐먼트에 읽기 작업에서만 보장
    • snapshot
      • 트랜잭션이 causally consistent session이 아니고 writeConcern이 majority인 경우, 트랜잭션은 과반이 커밋된 데이터의 스냅샷에서 읽음
      • 트랜잭션이 causally consistent session이고 writeConcern이 majority인 경우, 트랜잭션 시작 직전에 과반이 커밋된 데이터의 스냅샷에서 읽음
      • 멀티 도큐먼트 트랜잭션에서만 사용가능
      • 샤딩된 클러스터 중 하나라도 Disable Read Concern Majority 설정을 할 경우 사용할 수 없

writeConcern

  • readConcern와 결합하여 애플리케이션에 대한 일관성가용성을 제어할 수 있음.
  • 제공 수준(w)
    • majority
    • number - 0 or 1
    • custom write concern name

 

스키마 마이그레이션(migration)

  • 애플리케이션의 규모가 커지고 요구사항이 변할수록 스키마도 커지고 변환해야 함
    1. 스키마 애플리케이션의 요구에 맞춰 변화시키는 방법
    2. version 필드를 사용하는 방법
    3. 스키마가 변경될 때 모든 데이터를 마이그레이션하는 방법
728x90
반응형