본문 바로가기
MongoDB

[MongoDB] Ch9 - 애플리케이션 설계

by 잭피 2022. 1. 11.

애플리케이션을 몽고DB와 효율적으로 작동하도록 설계하는 방법을 다뤄보자

  • 스키마 설계 고려 사항
  • 데이터 내장 방식과 참조 방식 중 결정하기
  • 최적화를 위한 팁
  • 일관성 고려 사항
  • 스키마 마이그레이션 방법
  • 스키마 관리 방법
  • 몽고DB가 데이터 스토리지로 적합하지 않은 경우

9.1 스키마 설계 고려 사항

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

가장 좋은 설계 접근 방식은 애플리케이션에서 원하는 방식으로 데이터를 표현하는 방법이다

스키마 모델링 전, 먼저 쿼리 및 데이터 접근 패턴을 이해해야 한다

스키마 설계할 때 고려할 주요 요소

제약 사항

데이터베이스와 하드웨어 제약 사항을 이해해야 한다

도큐먼트의 최대 크기는 16MB이며, 디스크에서 전체 도큐먼트를 읽고 쓴다

갱신은 전체 도큐먼트를 다시 쓰며, 원자성 갱신은 도큐먼트 단위로 실행된다

쿼리 및 쓰기의 접근 패턴

워크로드를 식별하고 정량화해야 한다

애플리케이션의 읽기와 쓰기를 모두 포함한다

쿼리의 실행 시기, 빈도를 알면 가장 일반적인 쿼리를 식별할 수 있다

쿼리를 식별한 후에 쿼리 수를 최소화하고, 함께 쿼리되는 데이터가 동일한 도큐먼트에 저장되도록 설계를 확인해야 한다

이러한 쿼리에 사용되지 않는 데이터는 다른 컬렉션에 넣어야 한다

자주 사용하지 않는 데이터도 다른 컬렉션으로 이동하자

동적(읽기,쓰기) 데이터와 정적(대부분 읽기) 데이터를 분리할 수 있는지도 고려해보자

스키마 설계의 우선 순위를 가장 일반적인 쿼리에 지정할 때 결과가 최상의 성능을 가진다

관계 유형

요구 사항 측면과 도큐먼트 간 관계 측면에서 어떤 데이터가 관련돼 있는지 고려해야한다

데이터나 도큐먼트를 내장하거나 참조할 방법을 결정한다

추가로 쿼리하지 않고 도큐먼트를 참조하는 방법을 파악해야 한다

관계가 변경될 때 갱신되는 도큐먼트 개수를 알아야 한다

또한 데이터가 쿼리하기 쉬운 구조인지도 고려하자 (예를 들어 중첩 배열은 특정 관계 모델링을 지원)

카디널리티

카디널리티는 특정 컬럼에서 유니크한 데이터의 개수이다

도큐먼트와 데이터가 어떻게 관련돼 있는지 확인한 후에는 관계의 카디널리티를 고려해야한다

예를 들어 현재 관계가 일대일인지, 일대다인지, 다대다인지 고려한다

몽고DB 스키마에서 모델링에 최선의 형식을 사용하도록 관계의 카디널리티를 설정하는 것이 매우 중요하다

수백만 측면의 개체가 개별적으로 접근되는지 혹은 상위 개체의 컨텍스트에서만 접근되는지 고려해야 하며, 해당 데이터 필드에 대한 읽기 비율도 고려해야 한다

이런 문제에 대한 답은 도큐먼트 간에 데이터를 비정규화해야 하는지 여부와, 도큐먼트를 내장할지 혹은 참조할지 결정하는 데 도움이 된다

1. 스키마 설계 패턴

애플리케이션 성능에 직접 영향을 미치기 때문에 몽고DB에서 스키마 설계는 중요하다

다형성 패턴

컬렉션 내 모든 도큐먼트가 유사하지만 동일하지 않은 구조를 가질 때 적합하다

속성 패턴

정렬하거나 쿼리하려는 (공통 특성을 갖는) 도큐먼트에 필드의 서브셋이 있는 경우 or 정렬하려는 필드가 도큐먼트의 서브셋에만 존재하는 경우 적합하다

이 패턴은 도큐먼트당 많은 유사한 필드를 대상으로 지정하기 때문에 필요한 인덱스가 적어지고 쿼리 작성이 더 간단해진다

버킷 패턴

데이터가 일정 기간 동안 스트림으로 유입되는 시계열 데이터에 적합하다

이상치 패턴

인기도가 중요한 상황을 위해 설계된 고급 스키마 패턴으로, 주요 영향 요인, 도서 판매, 영화 리뷰 등이 있는 소셜 네트워크에서 볼 수 있다

플래그를 사용해 도큐먼트가 이상점임을 나타내며 추가 오버플로를 하나 이상의 도큐먼트에 저장한다

계산된 패턴

데이터를 자주 계산해야 할 때나 데이터 접근 패턴이 읽기 집약적일 때 사용한다

이 패턴은 주요 도큐먼트가 주기적으로 갱신되는 백그라운드에서 계산을 수행하도록 권장한다

서브셋 패턴

장비의 램 용량을 초과하는 작업 셋이 있을 때 사용한다

서브셋 패턴은 자주 사용하는 데이터와 자주 사용하지 않는 데이터를 두 개의 개별 컬렉션으로 분할하도록 한다

예를 들어 가장 최근 리뷰 10개를 자주 접근하는 컬렉션에 저장하고 나머지 리뷰는 다음 컬렉션에 저장한다

확장된 참조 패턴

개별 컬렉션에서 단일 주문에 대한 정보를 모두 수집하면 성능에 부정적인 영향을 미칠 수 있다

이때 자주 접근하는 필드를 식별하고 주문 도큐먼트를 복제하면 문제를 해결할 수 있다

확장된 참조 패턴은 데이터를 중복시키는 대신 정보를 조합하는 데 필요한 쿼리 수를 줄인다

근사 패턴

리소스가 많이 드는 (시간, 메모리, CPU 사이클) 계산이 필요하지만 높은 정확도가 반드시 필요하지 않은 상황에 유용하다

이미지나 게시글의 추천 수 카운터 또는 페이지 조회 수 카운터를 예로 들 수 있다

트리 패턴

쿼리가 많고 구조적으로 주로 계층적인 데이터가 있을 때 적용한다

동일한 도큐먼트 내 배열에 계층구조를 쉽게 저장할 수 있다

사전 할당 패턴

주로 MMAP 스토리지 엔진과 함께 사용됐지만 여전히 사용된다

빈 구조를 사전 할당한다

ex) 예약 정보를 매일 관리하는 시스템 (예약 가능 여부, 현재 예약 상태 추적)

도큐먼트 버전 관리 패턴

도큐먼트의 이전 버전을 유지하는 메커니즘을 제공한다

도큐먼트 버전을 추적하려면 각 도큐먼트에 부가 필드를 추가해야 하며 도큐먼트의 모든 수정 사항을 포함하는 추가 컬렉션이 필요하다

9.2 정규화 vs 비정규화

정규화는 컬렉션 간의 참조를 이용해 데이터를 여러 컬렉션으로 나누는 작업이다

몽고DB 집계 프레임워크는 소스 컬렉션에 일치하는 도큐먼트가 있는 결합된 컬렉션에 도큐먼트를 추가해 왼쪽 외부 조인을 수행하는 $lookup 단계와의 조인을 제공한다

비정규화는 모든 데이터를 하나의 도큐먼트에 내장하는 것으로, 정규화의 반대다

여러 도큐먼트가 최종 데이터 사본에 대한 참조를 갖는 대신에 데이터의 사본을 가진다

정보가 변경되면 여러 도큐먼트가 갱신돼야 하지만, 하나의 쿼리로 모든 데이터를 가져올 수 있음을 뜻한다

1. 데이터 표현 예제

일반적인 RDBMS에서는 조인 테이블을 만들지만, 몽고DB에서는 참조를 내장함으로써 역참조하는 쿼리 중 하나를 제거할 수 있다

읽기를 좀 더 최적화하려면 데이터를 완전히 비정규화하고 각 과목을 필드에 내장 도큐먼트로 저장해 하나의 쿼리로 모든 정보를 가져오게 할 수 있다

이때 장점은 쿼리를 하나만 사용해 정보를 얻는다는 점이다

반면 더 많은 공간을 차지하고 동기화하기 더 어렵다는 단점도 있다

확장 참조 패턴을 사용해서 참조값을 포함해 내장할 수 있다. 추가로 필요한 정보는 실제 도큐먼트를 참조하면 된다

또한 정보가 읽히는 빈도에 비해 얼마나 자주 변경되는지도 고려해야 한다

정보가 정기적으로 갱신돼야 한다면 정규화하는 것이 좋다

2. 카디널리티

카디널리티는 컬렉션이 다른 컬렉션을 얼마나 참조하는지 나타내는 개념이다

일반적인 관계는 일대일, 일대다 혹은 다대다다

많고 적음의 관계를 결정하면 무엇을 내장할지 결정하는 데 도움이 된다. 일반적으로 ‘적음’ 관계는 내장이 더 적합하고 ‘많음’ 관계는 참조가 더 적합하다

9.3 데이터 조작을 위한 최적화

애플리케이션을 최적화하려면 읽기와 쓰기 성능을 분석해 어느 것이 병목 현상을 일으키는지 우선적으로 알아야 한다

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

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

빠른 쓰기에 최적화된 스키마와 빠른 읽기에 최적화된 스키마 사이에는 종종 트레이드오프가 존재한다

따라서 어느 것이 애플리케이션에 더 중요한지 결정해야 한다

1. 오래된 데이터 제거

오래된 데이터를 제거하는 데는 일반적으로 3가지 방법을 사용한다

  1. 제한 컬렉션을 사용
    • 제한 컬렉션 크기를 크게 설정하고 오래된 데이터가 끝으로 밀려나게 한다
    • 컬렉션이 유지되는 시간을 일시적으로 줄이기 때문에 급격히 증가하는 트래픽에 취약하다
  2. TTL 컬렉션 사용
    • 도큐먼트가 제거될 때 미세하게 조절할 수 있다
    • 그러나 쓰기를 매우 많이 수행하는 컬렉션에 사용하기에 충분히 빠르지 않다
    • 사용자 요청 제거와 같은 방식으로 TTL 인덱스를 탐색해 도큐먼트를 제거한다
  3. 주기마다 컬렉션을 삭제
    • 한 달에 하나의 컬렉션을 사용할 때 컬렉션을 나눠 관리할 수 있다
    • 이 방법을 사용하면 어떤 양의 트래픽에도 대부분 버틸 수 있지만, 동적 컬렉션 이름을 사용해 여러 데이터베이스를 조회하므로 애플리케이션 구축이 좀 더 복잡하다

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

일반적으로 스키마가 유사한 도큐먼트는 같은 컬렉션에 보관해야 한다

몽고DB는 보통 서로 다른 컬렉션에 있는 데이터의 결합을 허용하지 않는다

함께 쿼리하거나 집계해야 하는 도큐먼트는 하나의 큰 컬렉션에 넣는 것이 좋다

컬렉션에는 락과 저장을 중요하게 고려해야 한다

일반적으로 쓰기 워크로드가 높다면 여러 물리적 볼륨을 사용해 입출력 병목 현상을 줄일 수 있다

—directoryperdb 옵션을 사용하면 데이터베이스는 각자의 디렉터리에 있으므로 서로 다른 데이터베이스를 서로 다른 볼륨에 마운트할 수 있다

그러므로 데이터베이스 내 모든 항목이 비슷한 품질, 비슷한 접근 패턴, 비슷한 트래픽 수준을 갖는 것이 좋다

$merge 연산자를 사용하면 데이터베이스의 집계 결과를 다른 데이터베이스 혹은 다른 컬렉션에 저장할 수 있다

추가로 주의할 점은 데이터베이스의 컬렉션을 다른 데이터베이스로 복사할 때 renameCollection 명령을 사용하면 새 데이터베이스에 모든 도큐먼트를 복사해야 하므로 속도가 느리다는 점이다

9.5 일관성 관리

다양한 수준의 일관성을 얻는 방법을 이해하려면 몽고DB 내부에서 무엇을 수행하는지 이해해야 한다

서버는 각 연결에 대한 요청 큐를 보관한다

클라이언트가 요청을 보내면 요청은 연결 큐의 가장 마지막에 위치하게 된다

이후 요청은 이전에 큐에 추가된 작업이 진행된 후에 발생한다

셸을 2개 열면 데이터베이스에 대한 연결이 2개가 된다

하나의 셸에서 삽입을 수행하면 이후에 다른 셸에서 발생하는 쿼리는 삽입된 도큐먼트를 반환하지 못한다

그러나 단일 셸에서 삽입 작업 후에 쿼리하면 삽입된 도큐먼트가 반환된다

이 동작은 손으로 복제하기 어려울 수 있지만 분주한 서버에서 교차 삽입과 쿼리가 발생할 수 있다

이런 현상은 한 스레드에서 데이터를 삽입한 후 다른 스레드에서 해당 삽입의 성공 여부를 확인할 때 일어난다

잠시 동안은 데이터가 삽입되지 않은 것처럼 보였다가 갑자기 데이터가 나타난다

이런 동작은 루비, 파이썬, 자바 드라이버를 사용할 때 염두해 둘 만하다

세 언어 모두 커넥션 풀링을 사용하기 때문이다

드라이버는 효율성을 위해 서버에 대한 여러 연결(풀)을 열고 요청을 분산한다

하지만 세 드라이버 모두 일련의 요청이 하나의 연결에 의해 처리되도록 보장하는 메커니즘을 가진다

읽기 요청을 복제 셋의 세컨더리로 보낼 때 이는 큰 문제가 될 수 있다

세컨더리는 초, 분, 심지어 시간 단위 전부터 데이터를 읽으므로 프라이머리에 뒤처질 수 있다

해결 방법은 여러 가지가 있으며, 데이터가 오래돼 쓸 수 없게 될까 걱정된다면 모든 읽기 요청을 프라이머리에 보냄으로써 쉽게 해결할 수 있다

몽고DB는 읽을 데이터의 일관성과 격리 속성을 제어하는 readConcern 옵션을 제공한다

writeConcern과 결합하면 애플리케이션에 대한 일관성과 가용성 보장을 제어할 수 있다

(local, available, majority, linearizable, snapshot 이라는 5개의 수준이 있다)

애플리케이션에 따라 읽기 부실을 방지하려면 majority를 사용한다

majority는 대부분 복제 셋 멤버에서 확인된 내구성 있는 데이터만 반환하며 롤백되지 않는다

9.6 스키마 마이그레이션

애플리케이션 규모가 커지고 요구 사항이 변할수록 스키마 또한 커지고 변화해야 한다

각 도큐먼트에 version 필드를 추가해서 관리할 수 있다

9.7 스키마 관리

갱신 및 삽입 중에 유효성 검사를 허용한다

$jsonSchema 연산자를 사용하는 JSON 스키마 유효성 검사가 추가됐으며, 몽고DB의 모든 스키마 유효성 검사에 권장된다

유효성 검사는 기존 도큐먼트가 수정되기 전에는 확인되지 않으며 컬렉션별로 구성된다

9.8 몽고DB를 사용하지 않는 경우

몽고DB가 대부분의 애플리케이션에 잘 작동하는 범용 데이터베이스긴 하지만 모든 상황에 적합하지는 않다

몽고DB가 부적합한 경우

  • 다양한 유형의 데이터를 여러 차원에 걸쳐 조인하는 작업
    • 관계형 데이터베이스가 더 적합하다
  • 몽고DB보다 관계형 데이터베이스를 사용하는 가장 큰 이유는 몽고DB를 지원하지 않는 도구를 사용할 수 있기 때문이다
    • SQL Alchemy에서 워드프레스까지, 몽고DB를 지원하지 않는 도구는 수천 개 있다
    • 지원하는 도구가 늘고 있긴 하지만, 아직 관계형 데이터베이스 생태계를 따라가지 못한다

'MongoDB' 카테고리의 다른 글

[MongoDB] Ch11 - 복제 셋 구성 요소  (0) 2022.01.21
[MongoDB] Ch10 - 복제  (0) 2022.01.18
[MongoDB] Ch8 - 트랜잭션  (0) 2022.01.04
[MongoDB] Ch7 - 집계 프레임워크  (0) 2022.01.04
[MongoDB] Ch6 - 특수 인덱스와 컬렉션 유형  (0) 2021.12.24

댓글