샤딩 알고리즘
청크
각 컬렉션은 샤드 키를 기준으로 잘게 쪼개져 여러 샤드에 분산되어 관리된다.
이렇게 쪼개진 컬렉션의 조각(파티션)들을 청크라고 한다.
청크는 샤드 키의 원본 값 또는 해시 값의 일정 범위를 가진다.
샤드 키에서 가장 작은 값은 MinKey, 가장 큰 값은 MaxKey 로
도큐먼트의 필드가 가질수 있는 최솟값과 최댓값을 지칭하는 가상의 값을 의미하는 예약어인데
모든 청크는 MinKey 와 MaxKey 사이의 각 영역을 담당하게 된다.
청크는 어떠한 물리적인 의미를 가지지 않으며, 논리적으로만 존재하는 개념이다.
즉 청크 단위로 데이터파일이 생성되거나 데이터가 모여있지 않다.
실제로 청크에 관계없이 하나의 컬렉션에 속한 데이터는 하나의 데이터 파일에 섞여서 존재한다.
물론 컬렉션의 각 인덱스는 개별 파일에 저장되기는 하지만, 청크 단위로 다른 파일로 분리되어서 저장되지는 않는다.
만약 각 청크가 개별 데이터 파일로 저장된다면 데이터 파일의 개수가 매우 많아질 것이고,
그로 인해 데이터를 검색할 때 수많은 파일을 다 검색해야만 데이터를 찾을 수 있게된다.
실제 이렇게 물리적인 파일로 청크가 저장된다면 세컨드리 인덱스에 대한 지원을 하지 못할것이다.
청크는 컨피그 서버의 메타 데이터로만 존재하고, 실제 샤드 서버는 청크라는 개념에 대해 알 필요가 없다.
하지만 논리적으로만 존재하는 개념이기 때문에 샤드 간 청크 밸런싱 문제가 발생할 수 있다.
불균형이 발생하면 청크가 다른 샤드로 옮겨지게 되고, 이로인해 샤드 서버에서는 INSERT / DELETE 가 발생한다.
MongoDB 의 청크는 컬렉션의 도큐먼트 자체를 파티션하는 개념이며 세컨드리 인덱스까지 파티션하는 개념은 아니다.
물론 세컨드리 인덱스는 도큐먼트가 다른 샤드로 옮겨지면 그 도큐먼트를 가리키는 인덱스 엔트리도 같이 다른 샤드로 옮겨진다.
여기에서 샤딩과 세컨드리 인덱스가 무관하다는 말은 세컨드리 인덱스 자체도 샤딩을 하거나 청크를 나누는 것은 불가능하며 적용되지 않음을 의미한다.
즉 A 샤드 서버에 있는 세컨드리 인덱스 엔트리가 B 샤드 서버에 있는 도큐먼트를 가리킬 수는 없으며,
항상 세컨드리 인덱스 항목은 컬렉션의 도큐먼트에 종속되어 샤드 서버에 저장된다.
MongoDB 샤드 클러스터에서 세컨드리 인덱스는 항상 로컬 인덱스로 간주된다.
청크는 기본값으로 64MB 까지 커질 수 있으며, 그 이상으로 커지면 밸런서에 의해 자동으로 스플릿된다.
청크가 빈번하게 스플릿되면 각 샤드 서버 간의 청크 개수가 불균형가게 될 가능성이 높고,
균형이 맞지 않으면 밸런서가 청크를 이동시키면서 계속 규형된 상태를 유지한다.
청크 이동 자체가 매우 고비용이기 때문에 청크 이동 발생 시 서버의 부하를 장시간 유지하게 된다.
또 청크가 크면 클수록 샤드 간 부하를 조절하기가 어려우며, 청크가 작으면 작을수록 청크이동이 빈번하게 발생할 수 있다.
레인지 샤딩
MongoDB 에서 청크의 범위를 표시할 때는 대괄호와 소괄호를 구분해서 사용하는데, 이는 경계에 있는 값을 포함하는지 포함하지 않는지 표시하기 위함이다.
대괄호 [ ] 는 경계값이 현재 범위에 포함되는 것을 의미하며 소괄호 ( ) 는 포함하지 않는것을 의미한다.
샤드 키 값은 MinKey 보다 작을 수 없으며 MaxKey 보다 클 수 없다.
MinKey 와 MaxKey 는 범위를 표기하기 위해 만든 의사 값이다.
청크는 MinKey 는 포함할 수 있지만 MaxKey 는 포함할 수 없다. 다만 가상의 최솟값과 최댓값이므로 포함/불포함의 여부는 의미를 가지지 않는다.
레인지 샤딩 예제에서 가장 중요한 부분은 샤드 키 값이 별도의 변형 과정을 거치지 않고 그 자체로 정렬돼서 각 청크의 범위가 결정된다. 이런 특성으로 인해 장단점이 결정되는데, 가장 큰 장점은 범위검색 쿼리를 타겟 쿼리로 실행할 수 있다.
문제점은 각 샤드에 데이터가 균형 있게 분산되지 않을 가능성이 높다는 것이다.
예를들어 특정문자로만 데이터가 들어오고 이 문자가 청크1번으로만 레인지가 잡혀있다면 특정 청크만 커지고 다른 청크는 아주 적은 데이터를 가지게 된다.
레인지 샤딩은 샤드 키 값이 아주 균등하게 분산돼 있지 않는 이상 언젠가는 청크나 샤드 간 데이터의 불균형이 발생할 가능성이 높다.
이렇게 데이터 분산의 불균형이 발생하면 MongoDB 는 청크를 스플릿하고, 그럼으로써 각 샤드 서버 간의 청크 개수가 불균형 상태가 된다. 불균형이 되면 밸런서는 청크를 다른 샤드 서버로 옮기는 작업을 계속 수행하는데 이로인해 샤드 서버의 자원의 사용률을 높이게 된다.
청크의 분산 상태는 MongoDB 라우터로 접속하여 db.collection.getShardDistribution() 명령으로 확인할 수 있다.
레인지 샤딩은 가능하다면 해시 샤딩을 사용하고 해시 샤딩을 사용할 수 없을 때 레인지 샤딩을 사용하는 것이다.
해시 샤딩
샤드 키 값을 그대로 청크 할당에 사용하는 것이 아닌, 샤드 키 값의 해시값을 이용하여 청크를 할당하는 샤딩 방식이다.
일반적으로 해시함수는 결과값이 전반적으로 골구루 분산될 수 있는 암호화 해시 함수를 주로 사용하는데 MongoDB 는 MD5 해시 함수를 사용한다.
MD5 는 보안상 역해시가 가능하나 샤드 키는 비밀번호 목적이 아닌 데이터의 분산이 주목적이기 때문에 특별히 문제되지는 않는다.
해시 샤딩은 샤드 키 값의 해시값을 계산한 다음 해시값의 앞쪽 64bit 만 잘라서 64bit 정수형으로 사용한다.
그래서 해시 샤드 키가 가질 수 있는 값의 범위는 -2^63 에서 2^63-1 이다.
중요한것은 해시 샤딩도 결국 레인지 샤딩의 일종이라는 것이다.
해시 샤딩의 청크도 64bit 정수의 연속된 값을 담당하고 있으므로 레인지 샤딩과 동일하게 볼 수 있지만
MD5 가 적용된 해시값은 지정된 범위 내에서 매우 균등하게 분포된 값을 반환하기 때문에 샤드 키 값이 비슷한 값을 가진다 하더라도 해시함수의 결과값은 매우다른 값을 반환한다.
이러한 이유로 해시 샤딩은 레인지 샤딩대비 아래 장점을 제공한다.
- 샤드 키 값이 특정 범위에 집중돼 있을 때 발생하는 데이터 불균형
- 연속된 샤드 키 액세스로 인한 특정 샤드 서버의 부하 편중
하지만 해시 샤딩이라 하더라도 샤드 키의 원본 값이 같은 경우에는 해시 결과값도 같다.
그래서 키 값의 다양성이 떨어지면 아무리 해시 샤딩을 적용해도 특정 청크로 몰릴 수 있다.
다만 해시 샤딩 사용은 제약사항이 많다.
- 범위 검색 쿼리는 브로드캐스트 쿼리로 실행
- 샤드 키 필드에 대해서 해시 인덱스를 생성해야 함
브로드캐스트로 실행되는 이유는 해원본 값이 아니다보니 범위 검색을 할 때 특정 청크를 한정할 수 없어서 타겟 쿼리로 실행하지 못한다.
두 번째 제약사항은 해시 샤딩을 하고자 하는 경우에는 샤드 키에 대해서 해시 인덱스의 제약 사항이 동일하게 적용된다는 의미이다.
- 단일 필드에 대해서만 해시 인덱스를 생성할 수 있음
- 멀티 키 필드에 대해서는 해시 인덱스 생성 불가
- 부동 소수점 필드는 소수점 이하를 버리고 해시 함수 수행
- 2^53 보다 큰 부동 소수점에 대해서는 해시 인덱스를 지원하지 않음
db.users.insert({
name : "matt",
country : "korea",
composite_field: {name: "matt", country: "korea"}
});
단일 필드에 대해서만 해시 인덱스를 생성
2개의 필드를 합쳐서 해시 인덱스를 생성하는 것을 불가능
(x) db.users.createIndex({name:"hashed", country:"hashed"});
(o) db.users.createIndex({compsite_field : "hashed"});
조회 시 해시 인덱스를 사용할 수 있고 타겟 쿼리로 처리할 수 있다.
(o) db.users.find({key:{ "name" : "matt", "country" : " korea" }});
두 번째 쿼리와 세 번째 쿼리는 해시 인덱스를 사용하지 못하며 타겟 쿼리로도 작동하지 못한다.
(x) db.users.find({"key.name" : "matt", "key.country" : " korea" }});
(x) db.users.find({key:{ "name" : "matt" }});
해시 인덱스의 두 번째 제약사항은 멀티 키 필드에 대해서 해시 인덱스를 생성하지 못한다.
따라 서 여러 개의 값을 가지는 배열필드에 대한 멀티 키 인덱스는 지원할 수 없다.
해시 인덱스를 가진 필드에 배열 값을 저장하려고 하면 오류가 발생한다.
해시 함수는 부동 소수점의 소수점 이하 부분은 버리고 해시 함수를 적용하는데, 이로 인해 2.1 과 2.2 와 같은 정수부가 같은 경우에는 동일한 해시 결과값을 보여준다.
따라서 부동 소수점을 최대한 정수화한 다음에 (배수곱으로 소수를 정수화) 샤딩이나 인덱스를 생성하는 것이 좋으나 번거롭다면 3.4 버전부터 지원되는 Decimal 타입을 고려한다.
해시 샤딩에서 가끔 문제 되는 부분은 로그 파일에 출력되는 메시지의 내용으로,
하나의 청크가 너무 커지면 다음과 같은 경고 메시지가 서버 에러 로그에 출력된다.
문제는 메시지에서 해시 함수의 결과값이 나오기 때문에 어떤 키 값인지 찾을 방법이 없어서 필드 값을 집계쿼리를 샐행해서 같은 user_name 필드 값을 가진 도큐먼트의 개수를 확인해야 한다.
또는 각 청크의 실제 사이즈를 확인해 볼수도 있지만 상당한 시간과 서버 자원이 소모된다.
해시 샤딩에서 해시된 결과값은 지정된 범위 내의 값이라는 것을 MongoDB 가 이미 알고 있기 때문에 청크를 미리 스플릿해둘 수 있다.
numInitialChunks 에는 미리 스플릿해 둘 청크의 개수를 지정한다.
만약 하나의 샤드 서버에서 생성하고 청크를 이동하려고 할 때 실패한다면 하나의 샤드에만 남아있을 수 있기 때문에 반드시 shardCollection 명령을 실행한 다음 여러 샤드 서버에 골구루 분산되었는지 확인한다.
해시 함수는 균등분포이기 때문에 스플릿 되는 시점이 한번에 몰릴 수 있는 단점도 있다.
물론 가능성이 높진 않지만 한 번에 몰리는걸 방지하기 위해 수동으로 사용량이 낮은 시점에 청크를 조금씩 미리 스플릿해두는 것도 안정된 서비스를 운영하는데 도움이 된다.
* mongodb 모니터링 시, 청크 별 스플릿 시점 (MB 사이즈 체크)
지역 기반 샤딩
지역 기반 샤딩은 레인지 샤딩이나 해시 샤딩처럼 독립적으로 사용할 수 있는 샤딩 방식이 아니라 레인지 샤딩이나 해시 샤딩과 반드시 함께 사용해야 한다.
또한 레인지 샤딩이나 해시 샤딩은 샤딩 알고리즘이 모든 데이터를 커버할 수 있어야 하지만 지역 기반 샤딩은 선택적으로 적용할 수 있다.
즉 지역 기반 샤딩은 관심 대상의 데이터에만 샤딩 알고리즘을 적용할 수 있다. 그래서 더욱 지역 기반 샤딩은 단독으로 사용할 수 없는 것이다.
지역 기반 샤딩은 레인지 샤딩이나 해시 샤딩을 적용한 상태에서 데이터를 저장할 샤드를 한번 더 조정할 수 있는 옵션이라고 이해해도 된다. 지약기반 샤딩은 국가나 지역 기반으로 데이터의 저장소를 분리하기 위함이었다.
그림 4-31 은 사용자 아이디 값을 이용해서 레인지 샤딩 알고리즘을 사용하는 클러스터의 청크 분산이다.
여기에서는 사용자의 아이디가 1부터 600까지만 가질 수 있고, 각 청크의 범위를 100씩 스플릿 해 두었다.
이 서비스는 대부분 사용자가 한국에 있고 나머지 일부 사용자가 미국에 있다고 가정하여 5개 청크 중 3개는 한국에 배치했다.
지역 기반 샤딩에서는 단순히 샤드 키의 범위로만 청크를 식별하는 것이 아니다, 샤드 키가 어느 지역에 속할지 결정하는 지역 범위도 사용한다.
300보다 작은 user_id 는 KR 이라는 태그가 붙은 샤드에 저장하도록 했고, 300보다 크거나 같은 아이디는 US 라는 태그가 붙은 샤드에 저장하도록 지역 범위를 설정했다.
지역 기반 샤딩을 사용하려면 레인지나 해시 샤딩을 한 상태에서 추가로 두 가지 준비가 더 필요하다.
샤드별로 태그를 할당하고, 샤드 키 범위별로 태그를 할당하는 것이다.
샤드에 태그를 할당하는 것은 addShardTag 명령을 이용하며, 범위별로 태그를 할당하는 것은 addTagRange 명령을 이용한다.
결국 샤드와 사용자 데이터는 태그를 연결 고리로 서로 매핑되는 것이다.
또 샤드 키의 범위가 반드시 연속되어야 하는 것은 아니다.
또한 지역 범위가 반드시 MinKey 부터 MaxKey 까지 모든 영역을 커버해야 하는 것은 아니다.
혹은 1개 이상의 태그와 다중으로 매핑될 수도 있다.
지역 기반 샤딩의 주요 사용 목적은 아래와 같다.
- 지역 기반으로 사용자 데이터 구분
- 특정 사용자 데이터를 지정된 샤드 서버로 구분해서 관리
- 샤드 서버의 클래스(샤드 서버의 처리 능력이나 저장공간)뼐로 저장할 데이터를 구분
모니터링 한다면 샤드에 연결된 태그 확인, 지역 범위 확인이 있다.
샤드 키
MongoDB 의 샤딩에서 가장 중요한 것은 데이터를 분산하는 기준인 샤드 키와 데이터를 어떤 방식으로 분산할 것인지 결정하는 샤딩 알고리즘이다.
샤드 키가 중요한 이유는 사용자 데이터를 여러 서버에 분산하는 방식을 결정하는 요소이기도 하지만, 더 중요한 이유는 한번 설정한 샤드 키는 컬렉션을 완전히 새로 생성하지 않는 이상 변경할 수 없기 때문이다.
샤드 키는 컬렉션 단위로 설정되며, 샤딩을 하지 않는 컬렉션에 대해서는 샤드 키가 필요하지 않다.
샤드 키는 두가지 특성을 가진다.
- 샤드 키가 설정된 컬렉션에 대해 새로운 샤드 키를 설정하는 것은 불가능하다.
- 컬렉션의 샤드 키가 되는 필드의 값은 NULL 이며 변경할 수 없다.
샤드키가 MongoDB 클러스터에 미치는 영향은 매우 광범위하다.
- 타겟 쿼리와 브로드 캐스트 쿼리 결정
소량의 데이터를 읽는 쿼리가 아주 빈번하게 유입되는 서비스에서는 가능하면 타겟 쿼리를 유도할 수 있도로 샤드키를 설계
- 각 샤드 서버의 부하 분산
각 샤드 서버는 균등하게 데이터를 분산해서 가지게 되지만, 균등한지 않은지는 각 샤드 서버가 가진 청크의 개수로만 판단.
특정 청크에 데이터가 몰리면 부하가 분산되지 않음
- 청크 밸런스 작업
밸런서는 각 샤드가 가진 청크의 개수를 비교해서 청크의 개수를 균등하게 유지하기 위해서 샤드 간 청크 이동을 실행한다. 그런데 샤드 간 청크 이동은 부하가 높은 작업이어서 사용자 쿼리의 성능에 악영향을 미친다. 또한 이런 형태의 샤딩에서 항상 INSERT 는 마지막 청크를 가진 샤드 서버에서만 실행되므로 하나으 ㅣ청크가 핫 존이되고 INSERT 성능은 아무리 많은 샤드를 추가한다 하더라도 빨라지지 않는다.
프라이머리 샤드
샤드 클러스터의 모든 데이터베이스는 프라이머리 샤드를 가진다.
프라이머리 샤드는 샤드 클러스터에서 샤딩되지 않은 컬렉션들을 저장하는 샤드를 의미하는데,
레플리카셋의 프라이머리와는 전혀 무관한다.
각 데이터베이스의 프라이머리샤드는 다음과 같이 config 데이터베이스의 databases 컬렉션을 조회하면 확인할 수 있다.
컬렉션의 primary 가 프라이머리 샤드 정보를 저장하는 필드다.
샤드 클러스터에서 처음 데이터베이스가 생성되면 MongoDB 는 각 샤드 중에서 데이터를 가장 적게 가진 샤드를 선택해서 생성되는 데이터베이스의 프라이머리 샤드로 설정한다.
MongoDB 샤드클러스터라고 해서 모든 컬렉션이 샤딩되어야 하는건 아니지만, 이렇게 샤딩되지 않은 컬렉션을 어느 샤드에 저장할지 결정해야 하는데, 이 때 기준이 되는 정보가 각 데이터베이스의 프라이머리 샤드다.
프라이머리 샤드는 다른 샤드로 옮겨질 수 있는데 이렇게 옮기는 작업은 특정 샤드를 제거하고자 할 때 필요하다.
removeShard 명령으로 해당 샤드의 샤딩된 컬렉션 데이터를 다른 샤드로 이동시키고 최종적으로 서비스에서 샤드를 제거할 수 있다.
제거하고자 하는 샤드가 샤딩되지 않은 컬렉션을 가지고 있다면 먼저 프라이머리 샤드를 다른 샤드로 옮겨야 한다.
이 때 이용하는 명령이 movePrimary 명령이고 MongoDB 라우터에서만 실행할 수 있다.
movePrimary 명령으로 옮겨지는 컬렉션에 대해서는 데이터의 일관성 보장을 MongoDB 가 책임지지 않는다.
즉 movePrimary 중 DML 이 실행될 때 중간에 변경된 데이터를 새로운 프라이머리 샤드로 적용해주지 않는다는 것이다.
또한 샤딩되지 않은 컬렉션이 새로운 프라이머리 샤드로 옮겨지고난 이후 해당 컬렉션의 인덱스 생성이 실행되어야 한다.
이 인덱스 생성은 포그라운드 모드로 생성되므로 인덱스가 생성되는 시간 동안은 해당 컬렉션과 그 컬렉션이 가진 데이터베이스에 대해서 읽기와 데이터 변경을 수행할 수 없다.
프라이머리 샤드의 이동에서 또 하나 주의해야 할 점은 연결된 모든 MongoDB 라우터에게 특정 데이터베이스의 프라이머리 샤드가 변경됐다는 것을 알려줘야 한다.
그렇지 않으면 기존의 MongoDB 라우터는 샤딩되지 않은 컬렉션의 데이터를 읽고 변경하기 위해 기존의 프라이머리 샤드로 계속 접근하게 된다.
프라이머리 샤드가 변경된 것을 MongoDB 라우터가 새로 갱신하도록 하는 방법은 2가지가 있다.
- 모든 MongoDB 라우터 재시작
- 모든 MongoDB 라우터에서 flushRouterConfig 명령 실행
flushRouterConfig 는 프라이머리 샤드 정보만 갱신하는 것이 아니라 샤딩된 모든 컬렉션의 메타 정보를 모두 갱신한다. 그래서 동시에 많은 라우터에서 실행할 땐느 컨
'Database > MongoDB' 카테고리의 다른 글
[MongoDB] 인덱스(1) (0) | 2024.07.26 |
---|---|
[MongoDB] 샤딩(3) (0) | 2024.07.22 |
[MongoDB] 샤딩 (0) | 2024.06.28 |
[MongoDB] 복제(2) (0) | 2024.05.26 |
[MongoDB] 복제 (0) | 2024.05.20 |