1. 스키마 설계 고려 사항
- 데이터 표현의 핵심 요소는 데이터가 도큐먼트에서 표현되는 방식인 스키마의 설계
- 가장 좋은 설계 접근 방식은 애플리케이션에서 원하는 방식으로 데이터를 표현하는 방법이므로 관계형 데이터베이스와 달리, 스키마를 모델링하기 전 먼저 쿼리 및 데이터 접근 패턴에 대한 이해 필요
다음은 스키마를 설계할 때 고려할 주요 요소입니다.
가. 제약 사항
- 도큐먼트의 최대 크기는 16MB이며, 디스크에서 전체 도큐먼트를 읽고 씀
- 갱신은 전체 도큐먼트를 다시 씀
- 원자성 갱신은 도큐먼트 단위로 실행됨
나. 쿼리 및 쓰기의 접근 패턴
- 애플리케이션 및 더 넓은 시스템의 워크로드를 식별하고 정량화해야 함
- 워크로드는 애플리케이션의 읽기와 쓰기를 모두 포함
- 쿼리가 실행되는 시기와 빈도를 알면 가장 일반적인 쿼리를 식별 가능하며 이는 스키마를 설계하는 데 필요한 쿼리
- 쿼리를 식별한 뒤에는 쿼리 수를 최소화하고, 함께 쿼리 되는 데이터가 동일한 도큐먼트에 저장되도록 설계를 확인해야 함
- 이러한 쿼리에 사용되지 않는 데이터는 다른 컬렉션에 넣어야 함
- 동적 (읽기/쓰기) 데이터와 정적 (대부분 읽기) 데이터를 분리할 수 있는지도 고려해야 함
다. 관계 유형
- 애플리케이션 요구 사항 측면과 도큐먼트 간 관계 측면에서 어떤 데이터가 관련돼 있는지 고려한 뒤 데이터나 도큐먼트를 내장하거나 참조할 방법을 결정해야 함
- 추가로 쿼리하지 않고 도큐먼트를 참조하는 방법을 파악해야 하며, 관계가 변경될 때 갱신되는 도큐먼트 개수를 파악해야 함
- 또한 데이터가 쿼리 하기 쉬운 구조인지도 고려 필요 i.g. 중첩 배열은 특정 관계 모델링을 지원
라. 카디널리티
- 도큐먼트와 데이터가 어떻게 관련돼 있는지 확인한 뒤에는 관계의 카디널리티를 고려해야 함
- i.g. 현재 관계가 일대일인지, 일대다인지, 다대다인지 고려 필요
- 몽고DB 스키마에서 모델링에 최선의 형식을 사용하도록 관계의 카디널리티를 설정하는 것이 매우 중요
- 또한 수백만 측면의 개체가 개별적으로 접근되는지 혹은 상위 개체의 컨텍스트에서만 접근되는지 고려해야 하며, 해당 데이터 필드에 대한 읽기 갱신 비율도 고려 필요
1.1 스키마 설계 패턴
- 몽고DB에서 스키마 설계는 애플리케이션 성능에 직접적인 영향을 끼치기 때문에 중요함
- 스키마 설계에서 흔히 발생하는 문제는 보통 알려진 패턴이나 `빌딩 블록`으로 해결 가능하므로 이러한 패턴을 설계 단계에서 하나 이상 함께 사용하는 것을 권장
적용할 수 있는 스키마 설계 패턴은 다음과 같습니다.
가. 다형성 패턴 (Polymorphic Pattern)
- 다형성 패턴은 컬렉션 내의 모든 도큐먼트가 유사하지만, 구조가 완전히 동일하지 않을 때 유용함
- 해당 패턴은 애플리케이션에서 실행되는 공통 쿼리를 지원하는 도큐먼트 내에서 공통 필드를 식별하는 과정을 포함
- 도큐먼트나 서브 도큐먼트의 특정 필드를 추적함으로써, 애플리케이션이 차이점을 관리하기 위한 데이터 및 코드 경로를 작성할 수 있으며 이는 클래스나 서브클래스 간의 차이점을 식별하는 데 유용함
- 결과적으로, 서로 다른 구조의 도큐먼트를 포함한 단일 컬렉션에서 간단한 쿼리를 통해 쿼리 성능을 향상할 수 있음
나. 속성 패턴 (Attribute Pattern)
- 이 패턴은 도큐먼트의 일부 필드만 정렬하거나 쿼리 할 때 적합하며 조건에 맞는 필드만 도큐먼트의 서브셋에 존재할 때 유용
- 데이터를 key-value 쌍의 배열로 재구성하고, 배열 요소에 인덱스 생성 가능
- key-value 쌍에 qualifier를 부가 필드로 추가 가능하여, 쿼리 작성이 간편해지고 인덱스 수가 줄어듦
다. 버킷 패턴 (Bucket Pattern)
- 버킷 패턴은 스트림으로 유입되는 시계열 데이터에 적합함
- 데이터를 특정 시간 범위로 묶어 버킷화하면, 시간당 도큐먼트 생성보다 효율적
- 예를 들어, 1시간 동안의 데이터를 단일 도큐먼트의 배열에 저장 가능
- 각 도큐먼트에는 버킷의 시작 및 종료 시간이 포함됨
라. 이상치 패턴 (Outlier Pattern)
- 해당 패턴은 드물게 발생하는 쿼리가 애플리케이션의 정상적인 패턴을 벗어날 때 사용
- 플래그를 사용해 도큐먼트가 이상점임을 표시하고, 추가 오버플로우를 다른 도큐먼트에 저장.
- 이는 오버플로우 도큐먼트를 검색하기 위한 추가 쿼리 생성에 사용됨
마. 계산된 패턴 (Computed Pattern)
- 데이터 접근이 읽기 집약적이거나 자주 계산이 필요할 때 사용
- 백그라운드에서 주요 도큐먼트를 주기적으로 갱신하여, 개별 쿼리마다 계산된 필드 및 도큐먼트의 근사치를 제공
- 동일한 계산의 반복을 방지하여 CPU 부담을 줄일 수 있음
바. 서브셋 패턴 (Subset Pattern)
- 작업 세트가 장비의 램 용량을 초과할 때 사용됨
- 자주 사용하는 데이터와 사용하지 않는 데이터를 두 개의 컬렉션으로 분할
- 예를 들어, 전자상거래 애플리케이션에서는 최근 10개의 제품 리뷰를 주요 컬렉션에 저장하고, 나머지는 별도의 컬렉션으로 이동시킴
사. 확장된 참조 패턴 (Extended Reference Pattern)
- 여러 엔티티를 모아야 할 때 사용
- 자주 접근하는 필드를 식별하여 주문 도큐먼트로 복제하면, 쿼리 수를 줄여 성능을 향상함
- 데이터 중복을 줄이고, 필요한 정보를 쉽게 검색 가능
아. 근사 패턴 (Approximation Pattern)
- 리소스가 많이 드는 계산이 필요하지만, 정확도가 덜 중요한 경우에 유용
- 예를 들어, 추천 수나 조회 수 카운터는 100회 단위로 갱신하여 쓰기 횟수를 줄일 수 있음
자. 트리 패턴 (Tree Pattern)
- 쿼리가 많고, 계층적인 데이터가 있을 때 사용
- 배열 내에 계층구조를 저장하여, 함께 쿼리 되는 데이터를 한데 모아 관리
- 예를 들어, 제품 카탈로그의 범주 구성을 쉽게 추적 가능
차. 사전 할당 패턴 (Preallocation Pattern)
- 주로 MMAP 스토리지 엔진과 함께 사용됨
- 예약 정보를 관리하는 시스템에서 사전 할당된 구조를 사용하여 가용성을 쉽게 확인하고 계산 가능
카. 도큐먼트 버전 관리 패턴 (Document Versioning Pattern)
- 도큐먼트의 이전 버전을 유지하는 메커니즘을 제공
- 메인 컬렉션의 도큐먼트 버전을 추적하며, 모든 수정 사항을 포함하는 추가 컬렉션 필요
- 수정 및 다른 패턴의 고려가 필요할 수 있음
2. 몽고DB 패턴 및 스키마 설계 자료
몽고DB는 패턴 및 스키마 설계에 대한 몇 가지 유용한 온라인 자료를 제공합니다.
- 데이터 모델링: https://learn.mongodb.com/catalog
MongoDB Course Catalog Homepage | MongoDB University
ILT: DA620: Languages, Drivers, Web Services 09/09/2024 Event Implement scalable, stateless web services backed by a MongoDB database to … View Details
learn.mongodb.com
- `패턴으로 설계하기` 블로그 시리즈: https://www.mongodb.com/blog/post/building-with-patterns-a-summary
Building with Patterns: A Summary | MongoDB Blog
Away From the Keyboard: Kyle Lai, Software Engineer 2 In “Away From the Keyboard,” MongoDB developers discuss what they do, how they keep a healthy work-life balance, and their advice for people seeking a more holistic approach to coding. In this artic
www.mongodb.com
2. 정규화 vs 비정규화
데이터베이스 설계에서 정규화와 비정규화는 데이터 저장 및 접근 방식을 결정하는 중요한 개념입니다.
몽고DB를 포함한 많은 데이터베이스 시스템에서 이 두 접근법은 각기 다른 장단점을 가지고 있으며, 특정 요구 사항에 따라 선택됩니다.
가. 정규화
정규화는 데이터를 여러 컬렉션으로 나누어 저장하고, 컬렉션 간의 참조를 통해 데이터를 연결하는 방식이며 각 데이터 조각이 하나의 컬렉션에 저장되어 여러 도큐먼트가 이를 참조할 수 있음
- 장점: 데이터를 변경할 때는 참조된 컬렉션의 한 도큐먼트만 갱신하면 되므로, 데이터 일관성을 유지하기 쉬움
- 몽고DB에서의 활용: 몽고DB의 집계 프레임워크는 $lookup 단계를 통해 Left Outer Join을 수행할 수 있으며 이는 소스 컬렉션과 일치하는 도큐먼트를 결합된 컬렉션에 추가하여, 각 일치된 도큐먼트에 새 배열 필드를 추가
- 이후, 재구성된 도큐먼트는 추가 처리를 받음
- 적용 상황: 일반적으로, 정규화는 쓰기 작업의 속도를 높이는 데 유리하며 데이터의 중복을 줄이고, 일관성을 유지하는 것이 중요할 때 주로 사용됨
나. 비정규화
비정규화는 모든 데이터를 하나의 도큐먼트에 내장하는 접근법으로 데이터의 사본을 여러 도큐먼트에 저장하여, 각 도큐먼트가 필요한 데이터를 직접 포함
- 장점: 정보가 변경될 때 여러 도큐먼트를 갱신해야 하지만, 하나의 쿼리로 관련된 모든 데이터를 가져올 수 있어 읽기 성능이 뛰어남
- 적용 상황: 비정규화는 읽기 작업이 빈번하고, 빠른 데이터 접근이 필요한 경우에 유리; 데이터의 중복이 허용되며, 일관성보다 성능이 중요한 상황에서 사용됨
다. 정리
정규화와 비정규화 사이의 선택은 주로 애플리케이션의 요구 사항에 따라 결정됩니다.
- 데이터 일관성과 유지보수가 중요하다면 정규화가 적합할 수 있고, 빠른 읽기 성능이 우선시될 경우 비정규화가 적합할 수 있음
- 이러한 결정은 데이터베이스의 사용 패턴과 성능 요구 사항을 면밀히 고려하여 이루어져야 함
2.1 데이터 표현 예제
학생과 학생이 수강 중인 과목에 대한 정보를 저장한다고 가정하겠습니다.
- students 컬렉션과 classes 컬렉션으로 표현
- 그리고 세 번째 컬렉션 (studentClasses)은 학생과 학생이 수강 중인 과목에 대한 참조를 포함
과목을 배열에 집어넣는 것이 더욱 몽고 DB스럽지만 실제 정보를 얻으려면 여러 번 쿼리해야 하기 때문에 일반적으로 데이터를 이런 방식으로 보관하지는 않습니다.
- students 컬렉션에서 학생을 쿼리 하고, studentClasses 컬렉션에서 과목 "_id"를 쿼리 하고, classes 컬렉션에서 과목 정보를 쿼리 하기 때문에 서버에 세 번 다녀와야 함
학생 도큐먼트에 과목에 대한 참조를 내장함으로써 역참조하는 쿼리 중 하나를 제거할 수 있습니다.
- "classes" 필드는 John Doe가 수강하는 과목의 "_id" 배열을 보관
- 해당 과목에 대한 정보를 조회하려면 classes 컬렉션에 "_id"로 쿼리 하는데, 두 개의 쿼리면 충분함
- 이는 즉시 접근하거나 변경할 필요가 없는 데이터를 구조화하는 데 사용하는 꽤 보편적인 방법
읽기를 좀 더 최적화하려면 데이터를 완전히 비정규화하고 각 과목을 "classes" 필드에 내장 도큐먼트로 저장해 하나의 쿼리로 모든 정보를 가져오게 개선할 수 있습니다.
- 이때 장점은 쿼리를 하남나 사용해 정보를 얻는다는 점
- 반면 더 많은 공간을 차지하고 동기화하기가 더 어렵다는 단점 존재
- ex) 물리학이 사실 4학점이라면 물리학을 수강하는 모든 학생의 도큐먼트를 갱신해야 함
마지막으로 앞서 언급한 내장과 참조가 혼합된 확장 참조 패턴을 사용할 수 있습니다.
- 자주 사용하는 정보로 서브 도큐먼트의 배열을 생성하고, 추가적인 정보는 실제 도큐먼트를 참조하는 방식
- 시간이 흐르면서 요구 사항의 변경에 따라 내장된 정보의 양이 계속 바뀔 수 있으므로 이런 방식도 좋은 선택지
- 한 페이지에 정보를 많거나 적게 포함하려면 도큐먼트 내 정보를 많거나 적게 내장하면 됨
정보가 읽히는 빈도에 비해 얼마나 자주 변경되는지도 중요하게 고려해야 합니다.
- 정보가 정기적으로 갱신돼야 한다면 정규화하는 것을 권장
- 하지만 드물게 변경된다면 애플리케이션이 수행하는 모든 읽기를 희생해 갱신 프로세스를 최적화해도 이득이 거의 없음
내장 도큐먼트를 사용하기로 결정하고 도큐먼트를 갱시해야 한다면, 모든 도큐먼트가 성공적으로 갱신되도록 보장하기 위해 cron 작업을 설정해야 합니다.
- i.g. 다중 갱신을 시도했지만 모든 도큐먼트가 갱신되기 전에 서버가 갑작스럽게 고장 날 수 있으므로 이를 감지해서 갱신을 재시도할 방법이 필요
갱신 연산자 측면에서 "$set"은 멱등이지만 "$inc"는 그렇지 않습니다.
- 멱등 연산은 한 번 시도하든 여러 번 시도하든 동일한 결과를 나타내므로 네트워크 오류가 발생했다면 연산을 재시도하기만 해도 갱신하기에 충분
- 멱등이 아닌 연산자의 경우 작업을 두 개로, 즉 개별적으로 멱등이며 재시도해도 안전한 작업으로 분리해야 함
- 첫 번째 작업에 고유한 보류 토큰을 포함하고, 두 번째 작업에서 고유한 키와 고유한 보류 토큰을 모두 사용하게 하면 됨
- 해당 접근 방식을 사용하면 각 updateOne 작업이 멱등이므로 "$inc"가 멱등이 될 수 있음
어느 정도까지는 더 많은 정보를 생성할수록 더 적은 정보를 내장해야 합니다.
- 내장된 필드의 내용이나 개수가 제한 없이 늘어나야 한다면 일반적으로 해당 정보는 내장되지 않고 참조돼야 함
- 댓글 트리나 활동 목록 등은 내장되지 않고 자체적인 도큐먼트로 저장돼야 함
마지막으로, 포함된 필드는 도큐먼트의 데이터에 포함돼야 합니다.
- 도큐먼트에 쿼리 할 때 결과에서 거의 항상 제외되는 필드는 다른 컬렉션에 속해도 됨
내장 방식이 좋은 경우 | 참조 방식이 좋은 경우 |
작은 서브 도큐먼트 | 큰 서브 도큐먼트 |
주기적으로 변하지 않는 데이터 | 자주 변하는 데이터 |
결과적인 일관성이 허용될 때 | 즉각적인 일관성이 필요할 때 |
증가량이 적은 도큐먼트 | 증가량이 많은 도큐먼트 |
두 번째 쿼리를 수행하는 데 자주 필요한 데이터 | 결과에서 자주 제외되는 데이터 |
빠른 읽기 | 빠른 쓰기 |
2.2 카디널리티
- 카디널리티는 컬렉션이 다른 컬렉션을 얼마나 참조하는지 나타내는 개념
- 일반적인 관계는 일대일, 일대다 혹은 다대다
- 몽고DB를 사용할 때는 `다수`라는 개념을 `많음`과 `적음`이라는 하위 범주로 나눌 경우 개념상 도움이 됨
- 예를 들어, 각 작성자가 게시물을 조금만 작성하면 작성자와 게시물은 일대소 (one-to-few) 관계
- 태그보다 게시물이 더 많으면 블로그 게시물과 태그는 다대소 (many-to-few) 관계
- 게시물마다 댓글이 많이 달려 있으면 블로그 게시물과 댓글은 이대다 관계
2.3 친구, 팔로워, 그리고 불편한 관계
- 많은 애플리케이션에서 사람, 내용, 팔로워, 친구 등을 연결하는데 이렇게 긴밀하게 연결된 정보를 내장할지 혹은 참조할지 적절히 결정하는 방법은 파악하기 까다로울 수 있음
- 일반적으로 팔로우하고, 친구 맺고, 찜하는 동작은, 한 사용자가 다른 사람의 알림을 구독하는 발행-구독 시스템으로 단순화 가능
- 따라서 구독자를 저장하는 작업과 이벤트와 관련된 모든 사람에게 알림을 보내는 작업은 능률적이어야 함
- 구독을 구현하는 전형적인 방법은 세 가지가 있음
가. 게시자를 구독자의 도큐먼트에 넣는 방법
사용자 도큐먼트가 있으면, 다음처럼 쿼리 해서 사용자가 관심 가질 수 있는 활동을 모두 조회 가능합니다.
db.activitied.find({"user": {"$in": user["following"]}})
하지만 새로 게시된 활동에 관심 있는 사람을 모두 조회하려면 모든 사용자에 걸쳐 "following" 필드를 쿼리해야 합니다.
나. 게시자 도큐먼트에 팔로워를 추가하는 방법
해당 사용자는 뭔가를 할 때마다 알림을 보내야 할 모든 사용자를 바로 알 수 있습니다.
- 하지만 팔로우하는 사람을 모두 찾으려면 users 컬렉션 전체를 쿼리해야 한다는 단점이 있음
"가", "나" 방법 모두 추가적인 단점이 따르는데, 사용자 도큐먼트를 더욱 크고 자주 바뀌도록 만듭니다.
- "following" 혹은 "followers" 필드는 심지어 반환될 필요가 없을 때가 많음
다. 좀 더 정규화하고 구독을 다른 컬렉션에 저장하는 방법
- 앞서 언급한 대로 모든 팔로워 목록이 필요한 케이스가 많지 않으므로 좀 더 정규화를 진행함에 따라 구독을 다른 컬렉션에 저장
- 이렇게까지 정규화하면 지나칠 때가 많지만, 자주 반환되지 않으면서 매우 자주 변하는 필드에 유용
- "followers"는 이런 방법으로 정규화하는 데 적합한 필드
3. 데이터 조작을 위한 최적화
- 애플리케이션을 최적화하려면 읽기와 쓰기 성능을 분석해 어느 것이 병목 현상을 일으키는지 우선적으로 알아야 함
- 읽기 최적화는 일반적으로 올바른 인덱스를 사용해 하나의 도큐먼트에서 가능한 한 많은 정보를 반환하는 것과 관련 있음
- 쓰기 최적화는 보통 갖고 있는 인덱스 개수를 최소화하고 갱신을 가능한 한 효육적으로 수행하는 것과 관련 있음
- 빠른 쓰기에 최적화된 스키마와 빠른 읽기에 최적화된 스키마 사이에는 종종 트레이드오프가 존재하므로, 어느 것이 애플리케이션에 더 중요한지 결정해야 함
- 쓰기와 읽기의 중요도뿐 아니라 이들의 비율 또한 최적화 요소
- 애플리케이션에서 쓰기가 더 중요하지만 각 쓰기에 대해 읽기를 수천 번 수행한다면 읽기를 먼저 최적화하는 것을 권장
3.1 오래된 데이터 제거
- 어떤 데이터는 짧은 시간 동안만 중요한데 이런 데이터들은 몇 주 혹은 몇 달 후에 저장 공간만 낭비하게 됨
- 오래된 데이터를 제거하는 데는 일반적으로 세 가지 방법을 사용함
- 제한 컬렉션을 사용
- TTL 컬렉션을 사용
- 주기마다 컬렉션 삭제
가. 제한 컬렉션 사용
- 가장 쉬운 방법
- 제한 컬렉션 크기를 크게 설정하고 오래된 데이터가 끝으로 밀려나게 하면 됨
- 하지만 제한 컬렉션을 사용하면 사용자의 작업에 어느 정도 제약이 생기며, 컬렉션이 유지되는 시간을 일시적으로 줄이기 때문에 급격히 증가하는 트래픽에 취약함
나. TTL 컬렉션 사용
- TTL 컬렉션을 사용하면 도큐먼트가 제거될 때 미세하게 조절 가능
- 사용자 요청 제거와 같은 방식으로 TTL 인덱스를 탐색해 도큐먼트를 제거
- 하지만 쓰기를 매우 많이 수행하는 컬렉션에 사용하기에는 충분히 빠르지 않음
- 그럼에도 TTL 컬렉션이 속도를 따라갈 수 있다면 아마 가장 쉬운 방법일 수 있음
다. 주기마다 컬렉션 삭제
- 한 달에 하나의 컬렉션을 사용한다고 가정했을 때 달이 바뀔 때마다 애플리케이션은 비어 있는 달 컬렉션을 사용하고, 현재와 이전 달의 컬렉션에서 데이터를 조회하기 시작
- 컬렉션이 특정 시간보다 오래되면 삭제
- 해당 방법을 사용하면 어떠한 양의 트래픽에도 대부분 버틸 수 있지
- 동적 컬렉션 이름을 사용해 여러 데이터베이스를 조회하므로 애플리케이션 구축이 좀 더 복잡함
4. 데이터베이스와 컬렉션 구상
- 도큐먼트 형태를 구상하고 나면 어떤 컬렉션 또는 데이터베이스에 넣을지 결정해야 함
- 일반적으로 스키마가 유사한 도큐먼트는 같은 컬렉션에 보관해야 함
- 몽고DB는 보통 서로 다른 컬렉션에 있는 데이터의 결합을 허용하지 않기 때문에 함께 쿼리 하거나 집계 해야 하는 도큐먼트는 하나의 큰 컬렉션에 넣는 것이 좋음
- 컬렉션에서는 락과 저장을 중요하게 고려해야 함
- 일반적으로 쓰기 워크로드가 높다면 여러 물리적 볼륨을 사용해 입출력 병목 현상을 줄일 수 있음
- --directoryperdb 옵션을 사용하면 데이터베이스는 각자의 디렉토리에 있으므로 서로 다른 데이터베이스를 서로 다른 볼륨에 마운트 가능하므로 데이터베이스 내 모든 항목이 비슷한 `품질`, 비슷한 접근 패턴, 그리고 비슷한 트래픽 수준을 갖는 것이 좋음
- 구성 요소를 중요도에 따라 세 개의 데이터베이스 (logs, activities, users)로 나눌 수 있음
- 이렇게 세 개로 분리하면 가장 중요한 데이터가 가장 적은 데이터라는 장점이 있음 i.g. 사용자는 아마도 데이터를 로그만큼 많이 생성하지는 않음
- 전체 데이터셋을 위해 SSD를 사용할 여유가 없을 수도 있지만 하나 정도 마련해 사용자에 사용하거나, RAID10은 사용자에, RAID0은 로그와 활동에 사용하는 것을 권장
5. 일관성 관리
- 애플리케이션의 읽기에 필요한 일관성이 어느 정도인지 파악 필요
- 몽고DB는 직접 쓴 데이터를 읽는 것부터 알려지지 않은 오래된 데이터를 읽는 것까지, 상당히 다양한 수준의 일관성을 제공
- 지난해의 활동 기록이 필요하다면 지난 며칠 간의 정확한 데이터만이 필요할 수 있지만
- 반대로 실시간 거래를 수행한다면 최근 쓴 데이터에 즉각적으로 읽기를 수행할 필요가 있음
- 이러한 다양한 수준의 일관성을 얻는 방법을 이해하려면 몽고DB가 내부에서 무엇을 수행하는지 이해 필요
- 서버는 각 연결에 대한 요청 큐를 보관
- 클라이언트가 요청을 보내면 요청은 연결 큐의 가장 마지막에 위치하게 됨
- 이후의 요청은 이전에 큐에 추가된 작업이 진행된 후에 발생
- 따라서 각 연결은 데이터베이스에 대해 일관적인 관점을 가지며 자신의 쓰기를 항상 읽을 수 있음
- 지금까지 큐는 하나의 연결에 대한 큐였지만 셸을 두 개 열면 데이터베이스에 대한 연결이 두 개가 됨
- 하나의 셸에서 삽입을 수행하면 이후에 다른 셸에서 발생하는 쿼리는 삽입된 도큐먼트를 반환하지 못 함
- 그러나 단일 셸에서 삽입 작업 후에 쿼리 하면 삽입된 도큐먼트가 반환됨
- 이러한 현상은 한 쓰레드에서 데이터를 삽입한 뒤 다른 쓰레드에서 해당 삽입의 성공 여부를 확인할 때 일어남 (잠시 동안은 데이터가 삽입되지 않은 것처럼 보였다가 갑자기 데이터가 나타남)
- 이러한 동작은 특히 루비, 파이썬, 자바 드라이버를 사용할 때 염두에 둘 만한데, 세 언어 모두 커넥션 풀링을 사용하기 때문
- 읽기 요청을 복제 셋의 세컨더리로 보낼 때 세컨더리는 초, 분, 심지어 시간 단위 전부터 데이터를 읽으므로 프라이머리에 뒤처질 수 있음
- 이는 모든 읽기 요청을 프라이머리에 보냄으로써 쉽게 해결 가능
- 몽고DB는 읽을 데이터의 일관성과 격리 속성을 제어하는 readConcern 옵션을 제공
- writeConcern과 결합하면 애플리케이션에 대한 일관성과 가용성 보장을 제어 가능
- "local", "available", "majority", "linearizable", 그리고 "snapshot"이라는 5개의 수준이 있음
- 애플리케이션에 따라 읽기 부실을 방지하려면 "majority"를 사용
- "majority"는 대부분의 복제 셋 멤버에서 확인된 내구성 있는 데이터만 반환하며 롤백되지 않음
- "linearizable"을 사용할 수도 있는데 읽기 작업을 시작하기 전에, 완료된 쓰기를 모두 반영하는 데이터를 반환
- 몽고DB는 "linearizable" readConcern으로 결과를 반환하기 전에 동시 실행되는 쓰기가 완료될 때까지 대기함
6. 스키마 마이그레이션
- 애플리케이션의 규모가 커지고 요구 사항이 변할수록 스키마 또한 커지고 변화해야 함
- 여기에는 몇 가지 방법이 있는데, 어느 방법을 선택하든 애플리케이션이 사용하는 각 스키마를 신중히 기록해야 함
- 이상적으로는 앞서 언급한 `스키마 설계 패턴`을 적용할 수 있는지 고려해야 함
- 가장 간단한 방법은 스키마를 애플리케이션의 요구에 맞춰 변화시키는 방법
- 이때 애플리케이션이 구 버전 스키마를 모두 지원하는지 확인 필요 i.g. 필드의 존재 혹은 부재를 허용하는지, 여러 가능한 필드 유형을 정상적으로 취급하는지
- 하지만 해당 기법을 사용하면 코드가 복잡해질 수 있는데, 예를 들어 스키마 버전이 서로 충돌하는 경우 매우 지저분해짐
- ex) 어떤 버전은 "mobile" 필드를 요구하고, 다른 버전은 "mobile" 필드를 요구하지 않지만 다른 필드를 요구하고, 또 다른 버전은 "mobile" 필드를 선택적으로 요구
- 이렇게 변환하는 요구 사항을 추적하는 것은 코드를 더 복잡하게 만듦
- 변화하는 요구 사항을 더 구조화된 방식으로 처리 가능
- 각 도큐먼트에 "version" 필드를 추가해, 애플리케이션이 도큐먼트 구조를 위해 무엇을 받아들일지 판단하는 데 사용함으로써 스키마를 더욱 엄격하게 적용 가능
- 만약 도큐먼트가 현재 버전이 아니라면 스키마의 다른 버전에서 유효해야 함
- 그러나 여전히 구 버전에 대한 지원 필요
- 마지막으로 스키마가 변경될 때 모든 데이터를 마이그레이션 하는 방법이 있지만 권장하는 방법은 아님
- 몽고DB는 시스템에 많은 부하를 주는 마이그레이션을 피하기 위해 동적 스키마를 갖도록 허용
- 그러나 모든 도큐먼트를 바꾸기로 결심했다면 모든 도큐먼트가 성공적으로 갱신됐는지 확인해야 함
- 몽고DB는 이러한 마이그레이션을 지원하는 트랜잭션을 지원함
- 몽고DB가 트랜잭션 중간에 충돌할 경우 이전 스키마가 유지됨
7. 스키마 관리
- 몽고DB는 버전 3.2에서 스키마 유효성 검사를 도입해 갱신 및 삽입 중에 유효성 검사를 허용함
- 버전 3.6에는 $jsonSchema 연산자를 사용하는 JSON 스키마 유효성 검사가 추가됐으며, 해당 방법은 이제 몽고DB의 모든 스키마 유효성 검사에 권장됨
- 유효성 검사는 기존 도큐먼트가 수정되기 전에는 확인하지 않으며 컬렉션별로 구성됨
- 기존 컬렉션에 유효성 검사를 추가하려면 validator 옵션과 함께 collMod 명령을 사용
- 새 컬렉션에 유효성 검사를 추가하려면 db.createCollection()을 사용할 때 validator 옵션을 지정
- 또한 몽고DB는 validationLevel, validationAct이라는 두 개의 추가 옵션을 제공
- validationLevel은 기존 도큐먼트 갱신 중에 유효성 검사 규칙을 얼마나 엄격하게 적용할지 결정
- validationAction은 불법 도큐먼트를 오류와 함께 거절할지 혹은 경고화 함께 허용할지 결정
8. 몽고DB를 사용하지 않는 경우
- 몽고DB가 대부분의 애플리케이션에 잘 작동하는 범용 데이터베이스긴 하지만 모든 상황에 적합하지는 않음
- 몽고DB는 다음과 같은 사례에는 부적합함
- 다양한 유형의 데이터를 여러 차원에 걸쳐 조인하는 작업은 RDBMS에 적합함
- 몽고DB보다 RDBMS를 사용하는 가장 큰 이유는 몽고DB를 지원하지 않는 도구를 사용할 수 있기 때문
- SQL 알케미에서 워드프레스까지, 몽고DB를 지원하지 않는 도구는 수천 개가 있음
참고
몽고DB 완벽 가이드 3판 - 한빛미디어
'DB > 몽고DB 완벽 가이드 3판' 카테고리의 다른 글
[11장] 복제 셋 구성 요소 (0) | 2025.04.24 |
---|---|
[10장] 복제 셋 설정 (0) | 2025.04.22 |
[8장] 트랜잭션 (0) | 2025.04.16 |
[7장] 집계 프레임워크 (0) | 2025.04.12 |
[6장] 특수 인덱스와 컬렉션 유형 (0) | 2025.04.10 |