청크 밸런싱
샤드 간 데이터 불균형 현상을 피하기 위해 최대한 각 샤드가 가진 청크의 개수를 동일하게 만드려고 한다.
그뿐만 아니라 하나의 청크가 너무 커지면 샤드 서버는 청크 하나의 이동에 너무 많은 시스템 자원을 소모한다.
이렇게 하나의 청크가 너무 비대해지는 것을 막기 위해 각 청크에 데이터가 저장되거나 변경될 때마다 청크를 스플릿해야할지 체크하는 작업을 진행한다.
샤드 클러스터 밸런서
밸런서는 샤드간 청크의 개수를 모니터링하다가 불균등해지면 적절히 청크를 옮겨서 샤드 간 부하의 균형을 맞춘다.
불균형을 판단하는 기준은 청크를 가장 많이 가진 샤드와 가장 적게 가진 샤드의 개수가 임계치 이상 차이가 나면 청크 이동을 실행한다.
전체 청크 수 | 기준 치 |
20개 미만의 청크를 가진 컬렉션 | 2 |
20~79개 사이의 청크를 가진 컬렉션 | 4 |
80개 이상의 청크를 가진 컬렉션 | 8 |
청크 이동이 시작되는 임계치를 정해둔 이유는 청크 이동비용이 매우 많이 드는 작업이기 때문이다.
데이터가 저장되고 삭제되면서 청크는 계속 커질 수 있고, 이로인해 청크가 스플릿되어 전체적으로 샤드의 청크 개수가 늘어날 수 있다.
모든 샤드의 청크가 항상 동일 시점에 스플릿되는것은 아니기 때문에 일시적으로 2~3개 이상의 차이가 발생할 수 있다.
이렇게 작은 차이가 일시적으로 발생할 때마다 청크를 이동하는 것은 샤드 서버에 너무 많은 부담을 주기 때문에 컬렉션이 가진 청크의 개수를 기준으로 임계치를 설정해둔 것이다.
일단 밸런서가 시작되면 각 샤드가 가진 청크의 개수 차이가 기준치 이하로 떨어질때까지 청크를 이동해서 분산시킨다.
예외적인 케이스로 청크 이동에 실패하게 되면 밸런서 역시 멈추게 된다.
이렇게 밸런서가 청크의 불균형을 감지하여 청크 이동을 시작하고 끝날 때까지를 밸런싱 라운드라고 한다.
3.2 버전까지는 MongoDB 라우터에서 밸런서가 실행되었는데 라우터 인스턴스는 많이 있을 수 있으므로 lock 잠금 경합으로 좋지 못했다.
3.4 버전부터 밸런서 작업을 컨피그 서버에서 수행하도록 아키텍처를 개선했다.
컨피그 서버에서는 프라이머리 멤버만 청크 분산을 담당하므로 청크의 이동을 하나의 멤버에서만 진행한다.
이로인해 잠금 경합도 없어졌으며, 컨피그 서버 자기 자신이 메타데이터를 가지고 있기 때문에 로컬 데이터를 변경하므로 가장 중요한 메타 데이터 변경 단계에서 실패할 가능성을 획기적으로 줄였다.
또 청크 밸런싱 작업에 대해 라우터가 관여하는 부분이 없어졌고 라우터는 오직 사용자 쿼리를 샤드로 전달하는 역할만 처리하므로 언제든지 재시작해도 되었다.
3.2 버전까지는 MongoDB 청크 밸런싱에서 한 시점에 하나만 실행할 수 있어 샤드가 많은 클러스터는 권장되지 않았다.
3.4 버전부터 청크의 이동이 다중으로 실행될 수 있게 개선되었다. 다만 다중이동이 허용된다 하더라도 청크 이동에 참여하는 샤드 서버가 같다면 동시에 실행될 수 없다.
이러한 이유는 청크 이동 자체가 샤드 서버에 많은 부하를 일으키기 때문에 하나의 샤드 서버는 한 시점에 하나만 처리하려고 제약을 둔 것이다.
여러 개 청크가 이동할 수 있는 밸런서의 작동 방식을 병렬 청크 마이그레이션이라 하며 컨피그 서버의 settings 컬렉션에서 밸런스 mode 가 full 로 표시된다.
현재 밸런서의 상태를 확인하는 방법은 isBalancerRunning 과 getBalancerState 이다.
isBalancerRunning 은 현재 밸런스가 청크 이동을 실행하거나 청크이동이 필요한지 모니터링 하고 있는지를 확인하고
getBalancerState 는 밸런서 자체가 작동 중인지 확인하는 명령이다.
setBalancerState 는 플래그값 변경으로 밸런서를 켜고 끄는 명령이다.
비활성화 시 그 시점부터 청크 밸런스 작업은 시작되지 않으나 이미 시작된 청크 이동 작업이 중간에 멈추지는 않는다.
반면 startBalancer 와 stopBalancer 는 밸런서를 중지하거나 시작하는데 밸런서가 완전히 멈추거나 시작할때까지 기다린다.
밸런서의 청크 이동은 상당히 많은 시스템 자원을 사용하기 때문에 특정 시간대에만 실행되도록 제어하는 기능이 있다.
먼저 config DB 로 이동 후 setting 컬렉션에서 balancer 와 관련된 도큐먼트의 activeWindow 정보를 UPDATE 한다.
waitForBalancer 명령을 사용하여 밸런서 작동여부에 따른 후속처리를 할 수 있다.
많은 데이터베이스와 컬렉션이 있을 때 특정 컬렉션만 청크 마이그레이션되기를 원할 수도 있다.
이렇게 특정 컬렉션만 밸런싱을 실행하거나 끄고싶을 땐 enableBalancing 과 disableBalancing 을 사용한다.
청크 스플릿
청크는 샤드 간의 데이터 균형을 위해서 관리되는 가장 작은 데이터 조각이다.
그래서 청크는 샤드 간을 이동할 때 샤드 서버에 최소한의 영향을 미치도록 적절한 크기를 유지해야 한다.
실제로 MongoDB 내부적으로 각 청크의 크기뿐 아니라 각 청크가 저장하고 있는 도큐먼트의 개수까지 균형을 이루도록 고려하고 있다.
MongoDB 청크는 자동 또는 수동으로 스플릿될수 있는데, 자동 스플릿은 청크에 도큐먼트가 INSERT 되거나 UPDATE 될때에만 해당 청크를 스플릿할지 결정하고 아래 2가지 기준을 따른다.
- 컨피그 서버의 settings 컬렉션에서 balancer 에 설정된 청크 사이즈보다 현재 청크의 크기가 클 때
- 청크 이동의 최대 도큐먼트 건수보다 많은 도큐먼트를 현재 청크가 가지고 있을 때
기본옵션으로 하나의 청크는 64MB 정도의 데이터 조각으로 유지하려고 한다.
그러나 서비스별 특성이나 잘못된 샤드 키 등의 이유로 특정 청크에 데이터가 집중될 수도 있다.
이렇게 특정 청크가 커지면 커진 청크를 쪼개서 2개 이상의 새로운 청크로 분리한다.
이렇게 하나의 청크를 여러 개의 새로운 청크로 쪼개는 작업을 스플릿이라 한다.
MongoDB 의 스플릿은 splitVector 와 splitChunk 두 단계로 나뉘어 처리된다.
splitVector 명령은 특정 청크에서 스플릿이 필요한지 확인하고, 만약 스플릿해야 한다면 스플릿을 실행할 위치의 배열을 리턴한다.
splitVector 명령이 실행되는 대략적인 과정이다.
1. 컬렉션의 전체 크기와 도큐먼트 개수를 이용해서 도큐먼트의 평균 크기 계산
2. 최대 청크 크기의 절반 크기에 저장될 수 있는 도큐먼트의 개수 계산
3. 청크의 크기가 청크의 최대 크기보다 작으면 splitVector 명령 종료
4. 청크에 저장된 샤드 키의 최솟값부터 인덱스를 스캔하면서 인덱스 키의 개수를 카운트한다.
5. 샤드 키 스캔이 완료되면 4번에서 기록된 샤드 키 값들을 배열로 리턴한다.
즉 splitVector 는 하나의 청크를 최대 청크 크기의 절반크기로 나누기 위해서 위치를 찾는과정이다.
그런데 만약 컬렉션의 샤드 키 값이 모두 한 값이라면 어떻게 될까?
이 컬렉션에 있는 도큐먼트 하나의 평균 크기가 1KB 이면 splitVector 명령은 32MB 개의 인덱스 키 단위로 스플릿 포인트를 기록한다.
그런데 모두 하나의 값이므로 splitVector 에서 이렇게 중복된 값들은 모두 무시된다.
동일한 샤드 키 값을 2개 이상의 청크로 쪼갤 수 없기 때문에 기록할 필요가 없는 것이다.
만약 32,768 번째 값이 다음값과 다르다면 이 지점을 스플릿 포인트로 기록하지만, 값이 모두 하나라면 이 청크는 스필릿 할 수 없다.
이런 경우 스필릿 포인트를 찾지 못했다는 메시지가 MongoDB 라우터나 컨피그 서버의 로그 파일로 기록된다.
splitVector 명령의 리턴 값이 청크를 스플릿할 수 있는 유효한 포인터를 포함하고 있다면 해당 지점을 기준으로 청크를 스플릿하도록 splitchunk 명령이 실행된다.
1. 컨피그 서버의 메타데이터 잠금 획득
2. 메타 데이터 변경을 위해서 배치 업데이트 명령 실행
3. 컨피그 서버의 changelog 컬렉션에 변경 이력 저장
splitVector 과정에서도 샤드 키 인덱스의 일부만 스캔한다는 것이다.
청크를 스플릿하기 위해 데이터파일을 모두 스캔하지 않는다.
그래서 청크의 스플릿 과정은 상대적으로 가볍게 처리된다.
게다가 splitVector 명령은 샤드 키 인덱스를 스캔하는 과정 중이 빈번하게 Yield 를 수행하는데, Yield 는 자기 자신이 가지고 있던 잠금을 모두 반납하고 잠시 실행을 멈추는 과정을 의미한다.
그래서 splitVector 명령이 장시간 실행되고 MongoDB 의 로그 파일에 슬로우 쿼리로 남는다고 하더라도 튜닝 대상으로 고려할 필요는 없다.
청크 스플릿은 자동으로 실행되기도 하지만, 관리자가 직접 특정 청크를 스플릿할 수도 있다.
가끔은 샤드 클러스터가 비대해진 청크를 스플릿하지 못할 수도 있다. 그리고 대량의 데이터를 적재하기 전에 미리 청크를 잘게 쪼개서 분산할 수도 있다. 이런경우 관리자가 직접 청크를 split 명령어로 스플릿한다.
다만 옵션(find, middle, bounds)에 따라 조금씩 용도와 스필릿 결과가 달라질 수 있다.
find : 주어진 조건을 이용하여 특정 청크를 찾은 다음 그 청크를 크기가 같은 2개의 청크로 스플릿한다.
find 옵션에 주어진 조건을 기준으로 청크를 2개로 나누는 것이 아니다.
middle : 주어진 조건을 이용해서 특정 청크를 찾은 다음 그 청크를 2개의 청크로 스플릿한다.
find 와 달리 middle 옵션에 주어진 샤드 키 값을 기준으로 청크를 스플릿한다.
bounds : 해시 샤딩을 사용하는 컬렉션을 스플릿하기 위한 명령이다.
bounds 옵션은 샤드 키의 범위를 배열로 명시하면 되는데, 이 배열은 스플릿 대상 청크의 최솟값과 최대값을 사용해야 한다. 또한 bounds 옵션에 사용되는 샤드 키 값은 해시되기 전의 원본값이 아니라 해시값을 사용해야 한다.
컨피그서버의 chunks 컬렉션에서 스플릿하고자 하는 청크의 최소값과 최대값을 이용하면 된다.
하지만 때로 모든 필드를 나열하지 못하거나 첫 번째 이후의 필드는 크게 중요하지 않을 수 있는데 이 때는 MinKey, MaxKey 를 사용한다.
청크 스플릿은 하나의 청크를 풀 스캔해서 스플릿 포인트를 찾아야 하므로 시간이 조금 걸릴 수 있다.
그런데 해시 샤딩과 같에 데이터의 분산이 너무 균등할 때는 여러 청크가 동시에 스플릿 시점이 되곤한다.
이런 경우 동시에 다수의 청크가 스플릿을 실행하면서 청크스플릿이 서비스 쿼리의 성능을 느리게 만들 수 있다.
그래서 MongoDB 서버에서는 청크 스플릿을 자동으로 실행하지 못하도록 비활성화 할 수 있다.
밸런서 -> 비활성 / 활성 ( 청크를 샤드에 맞춰 밸런싱 함 )
청크 스플릿 -> 비활성 / 활성 ( 청크를 나눔 )
비활성 / 활성에 따라 샤드별 청크가 어떻게 나뉘는지 테스트해보기
그리고 청크가 어느 샤드에 있는지, 청크 크기는 어떤지 볼 수 있는 방법을 체크해보기
청크 머지
때로는 컬렉션을 샤딩하면서 미리 청크를 스플릿해뒀는데, 의도한대로 데이터가 저장되지 않아서 빈 청크 상태이거나
데이터가 삭제되면서 빈 청크가 되기도 한다.
하지만 이렇게 빈 청크는 샤드 서버 간의 부하를 균등하게 유지하기 어렵게 만든다.
그래서 MongoDB 에서는 이렇게 데이터를 전혀 가지지 않는 청크를 주위의 연속된 청크와 병합할 수 있도록 mergeChunks 를 제공한다.
제약사항은 다음과 같다.
- 병합하고자 하는 청크는 같은 샤드에 존재해야 한다.
- 병합되는 청크 중에서 최소한 하나의 청크는 비어 있어야 한다.
- 연속된 샤드 키 범위의 청크만 병합할 수 있다.
청크가 비어있는지 아닌지는 dateSize 명령을 이용해서 확인할 수 있다.
결과값에서 size 와 numObjects 의 값이 모두 0 이면 해당 청크는 비어있는 것이다.
mergeChunks 시 기존의 청크 2개를 합쳐서 만드는 새로운 청크의 범위를 명시해야 한다.
청크 이동
밸런서는 각 샤드의 청크 개수를 비교해서 특정 샤드의 청크 개수가 다른 샤드에 비해서 많으면 샤드 간 청크 이동을 실행한다.
샤드 간 청크의 불균형을 감지하고 청크 이동을 실행하는 것은 밸런서지만
청크이동이 시작되면 청크를 가진 샤드와 청크를 전달받을 샤드 서버 간에 데이터 이동과 관련된 모든 작업이 처리된다.
실제 데이터 이동 자체에 대해 밸런서가 관여하는 부분은 없다.
청크의 이동은 여러 단계로 나뉘는데, 청크를 보내는 샤드에서 청크를 받는 샤드에 걸쳐 처리된다.
단계 | 처리 샤드 | 단계별 처리 내용 |
1 | 밸런서 | From Shard 로 moveChunk 명령실행 |
2 | From Shard | 이동 대상 청크의 상태와 moveChunk 의 파라미터 오류 체크 |
3 | From Shard | To Shard 에 From Shard 로부터 청크를 복사하도록 지시 |
4 | To Shard | From Shard 와 인덱스를 비교한 다음 필요하다면 인덱스 생성 |
5 | To Shard | From Shard 로부터 청크 데이터를 가져와서 저장 |
6 | To Shard | 5번 단계까지 실행하는 동안 변경된 데이터를 From Shard 로부터 가져와 저장 |
7 | To Shard | Steady 상태 |
8 | From Shard | 컨피그 서버의 청크 메타 데이터 변경 |
9 | From Shard | To Shard 로 이동된 청크의 데이터 삭제 |
청크의 이동 과정을 내부 상태별로 구분하면 다음과 같이 나눌 수 있다.
상태 | 설명 |
CLONE | 청크 데이터를 벌크로 가져와 복사하는 단계 아무런 잠금이 걸리지 않고, From Shard 에서 데이터 읽기 쓰기가 모두 가능 |
CATCHUP | CLONE 단계를 처리하는 중 From Shard 로 유입된 변경 내역을 To Shard 로 복사 아무런 잠금이 걸리지 않고, 읽기와 쓰기 모두 From Shard 에서 처리 |
STEADY | CATCHUP 에서 COMMIT_START 로 전이상태 STEADY 상태에서는 CATCHUP 상태처럼 From Shard 의 변경 내역을 계속 To Shard 로 전달받다가 From Shard 로부터 COMMIT_START 메시지가 오면 다음 단계로 상태를 전이하고 마지막으로 한번 더 From Shard 의 남은 변경 데이터를 To Shard 로 가져옴 |
COMMIT_START COMMIT |
메타 정보 업데이트 상태 From Shard 는 실제 변경된 샤드와 청크 정보를 컨피그 서버에 반영하는 작업을 수행 |
DONE | 청크의 이동이 완료된 상태 From Shard 는 이동된 청크의 데이터를 삭제 |
FAIL ABORT |
청크 이동 실패 혹은 포기 상태 |
청크가 원천에서 대상으로 옮겨진 이후에 샤드 서버나 컨피그 서버는 라우터에 청크 이동에 대한 정보를 넘겨주지 않는다.
그래서 라우터는 청크이동을 인지하지 못하고 여전히 해당 청크의 데이터를 조회하거나 변경하기 위해서 원천 샤드로 쿼리를 전달하게 된다. 하지만 그 청크는 이미 이동되었기에 쿼리는 실패한다. 이 때 원천샤드는 StaleConfigException 에러를 반환하는데 라우터는 이 에러를 받게 되면 즉시 컨피그 서버로 접속하여 해당 청크가 어디로 이동했는지 확인하고 대상 샤드로 쿼리를 다시 전달한다.
청크 마이그레이션 기록은 config 데이터베이스이 changelog 컬렉션에 저장된다.
이 때 2개의 도큐먼트를 기록하는데, 하나는 청크를 전송하는 쪽의 단계별 처리시간이며 다른 하나는 청크를 전송받는 쪽의 단계별 처리 시간이다.
또 청크가 완전히 이동한 건수는 what 필드의 값이 moveChunk.commit 인 도큐먼트의 건수를 확인하면 된다. 실패한건수는 moveChunk.commit 건수와 moveChun.start 건수의 차이를 확인하면 된다.
청크 아카이빙
MongoDB 2.6 버전부터 자동으로 이동된 청크 데이터를 movedChunk 라는 디렉터리에 컬렉션과 청크의 식별자로 명명된 파일로 백업한다. 이는 청크 이동이 실패했을 때 데이터를 복구할 수 있도록 구현된 일회성 백업 기능이다.
세컨드리 쓰로틀링과 청크 데이터의 비동기 삭제
청크 이동 쓰레드에 의해 변경된 데이터는 복제를 통해서 세컨드리 멤버로 전달된다.
그래서 청크 이동과 관련된 데이터 변경은 모두 OpLog 에 기록되어야 하고, 세컨드리에 상당한 영향을 미치게 된다.
그런데 프라이머리에서 청크 이동을 위한 DML 이 너무 빠른 속도로 진행되어 버리면 레플리카 셋의 세컨드리 멤버는 프라이머리 변경을 따라가지 못할 수 있다.
그래서 복제 지연을 막기 위해서 프라이머리와 세컨드리의 동기화 상태를 확인하면서 청크이동을 실행하도록 제한하는 기능을 추가했는데, 이 기능을 세컨드리 쓰로틀링이라고 한다.
이 기능이 활성화되면 청크 이동을 위한 도큐먼트 복사 작업이 프라이머리와 세컨드리 멤버까지 동기화되도록 한다.
즉 복사를 위해 {w:2} 이상 수준의 WriteConcern 모드를 사용하는데 세컨드리 멤버의 복제 지연을 최소화하는 효과를 낸다.
세컨드리 쓰로틀링 기능은 전체적으로 청크의 이동 속도를 느리게 만들지만, 세컨드리 동기화를 필요로 하는 사용자 쿼리의 응답 시간을 최대한 평상시와 비슷한 수준으로 할 수 있도록 해준다.
MongoDB 3.4 버전부터는 WiredTiger 스토리지 엔진을 사용하는 경우에는 세컨드리 쓰로틀링이 비활성화되어있고, MMAPv1 스토리지 엔진은 활성화되어있다.
그리고 수동으로 관리자가 직접 청크이동을 실행할 때도 moveChunk 명령과 함께 _secondaryThrottle 옵션으로 직접 쓰로틀링 여부를 명시할 수도 있다.
이렇게 명시된 옵션은 컨피그 서버 settings 컬렉션 balancer 도큐먼트에 설정된 쓰로틀링 옵션을 무시한다.
청크 마이그레이션 큐잉
청크 이동을 처리할 때 청크 이동의 여러 가지 절차 중 마지막 단계인 데이터 삭제 단계를 미뤄서 처리할 수 있는 옵션을 제공한다.
청크 이동은 기본적으로 청크를 보내는 샤드에서 데이터를 삭제하는 작업을 모두 완료해야 다음 청크 이동을 시작할 수 있다.
하지만 청크마이그레이션 큐잉 기능은 청크가 일단 다른 청크로 모두 옮겨지면 불필요한 데이터를 즉시 삭제하지 않고 큐에 담아둔다음 천천히 삭제하는 작업을 수행하도록 한다.
그리고 샤드 서버는 삭제와 관련된 정보를 큐에 담는 즉시 다른 청크 이동을 시작할 수 있다.
청크 이동에서 옮겨진 청크의 데이터를 삭제하는 작업을 지연해서 처리하는 방법은 컨피그서버 settings 컬렉션에서 변경하거나 관리자가 직접 moveChunk 명령을 실행하면서 변경할 수 있다.
청크의 데이터를 삭제하는 작업이 너무 많이 지연되어 큐잉된 삭제 작업이 너무 많이 남아있을 수 있다.
이렇게 큐잉된 삭제 작업을 완료하지 못한 상태에서 레플리카 셋의 프라이머리가 응답 불능이 되거나 비정상 종료를 하게되면 큐에 남은 정보도 같이 이러버리게 된다.
그러면 샤드 서버는 자기 자신이 관리하는 청크가 아님에도 실제 데이터가 자기 자신에 남게 되는 현상이 발생할 수 있는데 이런 도큐먼트를 고아 도큐먼트라고 표현한다.
데이터 삭제 작업을 뒤로 미루는 기능을 매뉴얼에서는 비동기 데이터 삭제라고 표현한다.
청크 이동 실패
모든 청크를 다른 샤드로 옮길 수 있는 것은 아니다.
청크 이동이 실패하는 이유는 다양한데, 청크 자체적 원인이 아닌경우 재처리로 가능하나 아래의 경우는 청크의 자체적인 원인으로 청크 이동이 불가능하다.
- 이미 다른 청크 이동이 실행되고 있는 경우
- 청크의 점보 청크 플래그가 활성화된 경우
- 청크가 너무 많은 도큐먼트를 가진 경우
도큐먼트가 많아도 실패하는데, 청크가 다른 샤드로 이동하려면 도큐먼트 제약조건을 만족해야 한다.
- 청크의 도큐먼트가 25만건 이하
- 청크의 도큐먼트가 (기본 청크 사이즈 / 평균 도큐먼트 크기) * 1.3 건 이하
여기에서 기본 청크 사이즈는 컨피그 서버 settings 컬렉션의 balancer 항목에 설정된 청크 크기이다.
평균 도큐먼트 크기는 db.collections.stat 명령에서 조회한 avgOvSize 필드 값을 의미한다.
이 조건을 만족하지 못하는 청크를 이동하려고 하면 Chunk too big to move 에러가 발생한다.
청크 사이즈 변경
청크 사이즈의 기본값은 64MB 이다.
일반적인 크기의 도큐먼트 사이즈에서는 적절한 기본값이다.
청크 사이즈는 샤드 클러스터에서 부하 분산의 가장 기본이 되는 옵션이며, 3가지를 결정하는 중요한 요소이다.
- 유연한 청크 이동
- 샤드 간 청크 이동 시 발생하는 부하 조절
- 샤드 간 부하 분산의 정도
청크의 크기는 하나의 청크가 가지는 도큐먼트의 개수를 결정하게 되는데, 도큐먼트 개수는 샤드 간 청크 이동이 가능한지 불가능한지 판단하는 요소로 사용한다.
또한 하나의 청크는 여러 샤드에 걸칠 수 없으므로 한번 이동이 시작된 청크는 서버의 부하에 상관없이 완료 혹은 취소되어야 한다. 그래서 청크사이즈가 크면 서버에 미치는 영향이 더 커지게 된다.
청크의 크기가 크면 클수록 클러스터에서 청크의 개수는 적어지지만 샤드 서버간 부하가 균등하지 않을 수 있다.
MongoDB 의 청크 사이즈는 1MB ~ 1024 MB 까지 설정할 수 있는데, config 데이터베이스의 settings 컬렉션을 변경함으로써 조정할 수 있다.
청크 사이즈를 변경할 때 자동 청크 스플릿은 다음과 같이 처리된다.
- 해당 청크에 INSERT / UPDATE 가 실행될 때만 자동 청크 스플릿이 실행된다.
- 청크 사이즈를 줄이는 경우 즉시 청크가 스플릿 되는것이 아니라 INSERT / UPDATE 가 실행될 때 새로운 청크 사이즈를 기준으로 스플릿된다.
- 청크 사이즈를 늘리는 경우 청크는 새롭게 설정된 청크 사이즈만큼 커질 때까지 스플릿되지 않는다.
-> 늘렸어, 물리적으로 청크 옆에 다른 청크가 바로 있어. 사이즈가 커질때만큼 스플릿이 안되면 인접하지 않는 서로다른 블럭에 저장되나 ?
점보 청크
단일 샤드 키 값으로만 구성된 청크의 크기가 설정된 청크 크기보다 크거나 혹은 작더라도 도뮤먼트 건수가 많은 경우, MongoDB 는 그 청크의 점보 청크 플래그를 활성화한다.
이렇게 점보 플래그가 활성화되면 그 청크는 더는 스플릿할 수 없을뿐 아니라 밸런서가 그 점보 청크를 다른 샤드로 옮기지 못한다.
점보 청크는 sh.status 나 config 데이터베이스의 chunks 컬렉션을 직접 쿼리해서 확인할 수 있으며, status 명령은 샤드 클러스터의 전체적인 정보와 모든 컬렉션의 샤딩과 관련된 정보를 보여주므로 많은 정보를 출력한다.
또 점보 필드가 true 인 청크만 찾아서 확인할 수도 있다.
이렇게 점보 플래그가 활성화된 청크는 적절한 크기로 스플릿해줘야 향후 청크가 더 커졌을 때 자동으로 스플릿될 수 있고, 밸런서가 필요할 때 청크를 다른 샤드로 이동할 수 있게된다.
스플릿이 가능한 점보청크
청크가 적절한 시점에 스플릿되지 못하거나 청크가 가진 도큐먼트 건수가 많으면 점보 청크가 되기도 한다.
sh.status(true) 나 config 데이터베이스의 chunks 컬렉션에서 점보 청크를 검색한 다음 sh.splitAt() 이나 sh.splitFind() 등의 청크 스플릿 명령으로 작은 청크들을 분리만 시켜두면된다.
청크 스플릿이 성공하면 MongoDB 는 자동으로 해당 청크의 점보 플래그를 제거하고 정상적인 청크로 취급한다.
스플릿이 불가능한 점보청크
하나의 샤드 키는 두 개 이상의 청크에 포함될 수 없다. 그래서 샤드 키 값이 동일한 도큐먼트들이 많이 저장되면 MongoDB 는 해당 청크를 스플릿하려고 하지만 결국 실패하고 점보 청크가 된다.
이렇게 스플릿이 불가능한 점보 청크가 많으면 컬렉션의 샤드 키를 잘 못 선택할 가능성이 높다.
점보 플래그가 활성화된 청크는 다른 샤드로 이동할 수 없기 때문에, 분산을 위해서는 컨피그 서버가 가진 메타 정보를 강제로 변경해야 한다.
이렇게 스플릿되지 못하는 점보 청크의 근본적인 해결책은 샤드 키를 변경하는 것인데, 컬렉션의 샤드 키를 변경하는 명령은 제공하지 않는다.
샤드 키를 변경하려면 서비스를 멈추고 컬렉션의 데이터를 모두 덤프한 다음 새로운 샤드 키로 생성된 컬렉션에 다시 적재해야 한다.
* 5.0 부터 가능 : https://www.mongodb.com/ko-kr/docs/manual/core/sharding-change-a-shard-key/
고아 도큐먼트 삭제
청크 이동이 실패하면 많은 도큐먼트가 고아 상태로 남게 된다.
고아 데이터가 많아지면 디스크의 데이터량이 커지고 시스템 자원도 비효율적이며 샤드에 직접 접근하는경우 결과에 오류를 발생할 수도 있다.
이 때 cleanupOrphaned 명령으로 고아 도큐먼트만 삭제할 수 있다.
인자로 주어진 startingFromKey 필드의 값부터 샤드 키를 스캔하면서 청크의 범위에 소속되지 않는 도큐먼트를 찾아서 삭제한다.
샤딩으로 인한 제약
트랜잭션
일반적인 트랜잭션 속성으로 ACID 를 주로 언급한다.
MongoDB 에서 트랜잭션을 지원하지 않는다고 언급하는 부분은 여러 개의 INSERT / UPDATE / DELETE 문장으로 구성된 트랜잭션을 지원하지 않는다는 의미이다.
단일 도큐먼트에 대한 변경은 모두 트랜잭션을 지원하지만 롤백할 수 있는 방법은 없다.
또 단일 도큐먼트는 원자성을 가지고 처리되지만 여러 도큐먼트를 변경하는 작업은 원자성을 가지지 않는다.
현재는 트랜잭션을 지원하고 있다.
https://www.mongodb.com/ko-kr/docs/v5.0/core/transactions/
샤딩과 유니크 인덱스
데이터가 샤딩되면 샤딩된 데이터 간의 유니크 인덱스 생성은 제약이 있다.
샤딩된 컬렉션에서 유니크 인덱스는 샤드 키를 포함하는 인덱스에 대해서만 적용할 수 있다.
프라이머리 키는 샤딩과 무관하게 항상 유니크해야 하며, 세컨드리 키 중에서도 유니크 옵션이 설정된 경우 중복허용하지 않도록 처리되어야 한다.
프라이머리 키의 중복체크 처리
프라이머리 키 필드에 사용자가 직접 값을 설정하는 경우 프라이머리 키의 유니크함을 사용자가 직접 보장해야 한다.
_id 필드의 값을 설정하지 않으면 자동으로 만들어서 저장한다. 다만 _id 필드를 사용자가 직접 선택한 값을 저장할 수 있는데 이 때는 사용자가 직접 중복되지 않는다는걸 보장해야 한다.
MongoDB 에서 중복체크 기능은 샤드 단위로만 체크하고 전체 샤드에 대해서 체크를 수행하지 않는다.
세컨드리 키의 중복체크 처리
샤드 키 기준의 필드가 선행이 되어야 한다.
샤드 키 기준의 필드가 선행인 복합 인덱스만 유니크 옵션을 설정할 수 있다.
하지만 해시 인덱스는 유니크 옵션을 설정할 수 없다.
그래서 해시 샤딩을 적용한경우에는 별도로 인덱스를 생성해서 유니크 옵션을 설정해야 한다.
조인과 그래프 쿼리
여러 컬렉션의 데이터를 조인해서 쿼리할 수 있도록 $lookup 오퍼레이션을 제공하고 있고
계층형 재귀 쿼리나 그래프 데이터를 쿼리할 수 있도록 $graphLookup 오퍼레이션을 지원한다.
이 때 모두 from 인자에 주어지는 컬렉션은 샤딩된 컬렉션을 사용할 수 없다.
이는 라우터가 아니라 샤드 서버에서 실행되는데, 샤드 서버는 데이터 처리를 위해 다른 샤드의 데이터를 참조할 수 없기 때문이다.
기존 컬렉션에 샤딩 적용
샤딩되지 않은 상태로 데이터를 가지고 있는 컬렉션을 샤딩할 때에는 샤딩을 적용할 수 있는 컬렉션의 사이즈에 제한이 있다.
샤딩되지 않은 컬렉션에 대해 샤딩을 적용하면 우선 그 컬렉션에 대해 청크 스플릿을 실행한다.
이 때 청크 스플릿을 위해 splitVector 명령을 실행하는데 이 때 실행되는 명령이 반환할 수 있는 결과는 하나의 BSON 도큐먼트로 반환된다.
명령의 결과가 BSON 도큐먼트이기 때문에 이 결과는 16MB 를 초과할 수 없다.
대략적으로 초기 샤딩을 적용할 수 있는 최대 컬렉션의 크기를 계산하는 방법이다.
최대 샤딩 가능한 컬렉션 크기 = ( 16MB / 샤드 키 길이 / 2 ) * 청크 사이즈
만약 이보다 큰 컬렉션에 대해 샤딩을 적용해야 한다면 청크 사이즈를 일시적으로 늘린 후 샤딩하면 된다.
샤딩이 완료되면 다시 청크 사이즈를 원래대로 되돌려서 설정한다.
'Database > MongoDB' 카테고리의 다른 글
[MongoDB] 인덱스(2) (0) | 2024.08.02 |
---|---|
[MongoDB] 인덱스(1) (0) | 2024.07.26 |
[MongoDB] 샤딩(2) (0) | 2024.07.05 |
[MongoDB] 샤딩 (0) | 2024.06.28 |
[MongoDB] 복제(2) (0) | 2024.05.26 |