1. 용도 평가
- 컬렉션을 샤딩할 때는 데이터 분할에 사용할 한두 개의 필드를 선택하며 해당 키를 샤드 키라고 지칭
- 컬렉션을 샤딩하고 나서는 샤드 키를 변경할 수 없으므로 올바르게 선택하는 것이 중요함
- 좋은 샤드 키를 선정하려면 샤드 키가 애플리케이션의 요청을 분산하는 방법과 작업량 이해가 필요함
- 샤드가 세 개인 클러스터는 샤드가 천 개인 클러스터보다 훨씬 유연함; 클러스터가 점점 더 커질 때, 쿼리가 모든 샤드를 방문해야 하는 쿼리를 피하기 위해서는 거의 모든 쿼리가 샤드 키를 포함해야 함
- 쓰기 응답 대기 시간을 줄이는 일은 일반적으로 요청을 지리적으로 더 가까운 곳이나 더 좋은 장비로 보내는 작업과 관련 있음
- 처리량을 늘리는 일은, 더 많은 병렬 처리 기능을 추가하고 클러스터에 요청을 균일하게 분산하는 작업과 관련 있음
- 시스템 리소스를 늘리는 목적으로 샤딩을 시도한다면 작업 셋의 크기를 가능한 한 작게 유지할 필요가 있음
- 샤드키를 선정할때 고려해야 할 사항은 아래와 같음
- 샤드 키가 필요한 타겟 쿼리를 제공하는가?
- 샤드 키가 시스템의 처치량이나 응답 대기 시간을 의도한 대로 변경하는가?
- 작은 작업 셋이 필요하면 샤드 키가 그것을 제공하는가?
2. 샤딩 구상
- 데이터를 분할할 때는 오름차순, 무작위, 위치 기반 키를 가장 일반적으로 사용함
- 다른 형태의 키도 사용할 수 있지만, 대부분의 사용 사례는 이 중 하나에 해당함
2.1 오름차순 샤드 키
- 오름차순 샤드 키는 일반적으로 "date" 필드나 ObjectId처럼 시간에 따라 꾸준히 증가하는 것이면 무엇이든 됨
- 자동 증가하는 프라이머리 키 또한 오름차순 샤드 키의 대표적인 예시
- ObjectId를 사용하는 컬렉션에 "_id"와 같은 오름차순 필드로 샤딩한다고 가정했을 때 "_id"로 샤딩하면 "_id" 범위의 청크로 분할됨
- 청크들은 샤드 클러스터에 분산됨
2.2 무작위 분산 샤드 키
- 무작위 분산 샤드 키는 오름차순 키와 대조되는 키
- 사용자명, 이메일 주소, UUID, MD5 해시 혹은 데이터셋에서 고유하지 않은 키는 모두 무작위 분산 샤드 키가 될 수 있음
- 쓰기 작업을 했을 때 무작위로 분산되므로, 발생할 수 있는 이동 횟수를 제한하면서 각 샤드가 거의 비슷한 비율로 커진다는 장점이 있음
- 하지만 몽고DB가 메모리 크기를 넘어서는 데이터를 임의로 접근하는 데 효율적이지 않다는 단점이 있음
- 가용 메모리가 있거나 성능 저하돼도 상관없을 경우 무작위 키는 클러스터에 부하를 분산하는 훌륭한 방법
2.3 위치 기반 샤드 키
- 위치 기반 샤드키는 사용자 IP, 경도와 위도, 주소 등이 될 수 있음
- 반드시 물리적 위치 필드와 관련될 필요는 없으며, "위치"는 데이터를 그룹화하는 추상적인 방법
- 어떤 경우든 위치 기반 키는 어떤 유사성을 갖는 도큐먼트가 해당 필드 기반의 범위에 포함되는 키이므로 데이터를 사용자와 가까운 곳에 두거나 관계있는 데이터를 디스크에 함께 보관하는 데 편리함
- 몽고DB는 영역 샤딩을 사용해 이를 관리함
- i.g. IP 주소로 샤딩 된 도큐먼트의 컬렉션이 있다고 가정했을 때 도큐먼트는 IP 기반 청크로 구성되며 클러스터에 무작위로 분산됨
- 특정 청크 범위를 특정 샤드와 연결하려면, 샤드를 영역화하고 청크 범위를 각 영역에 할당함
3. 샤드 키 전략
3.1 해시 샤드 키
- 데이터를 가능한 한 빠르게 로드하려면 해시 샤드 키가 최선의 선택
- 해시 샤드 키는 어떤 필드라도 무작위로 분산하기 때문에 쓰기를 무작위로 분산하려고 할 때 권장
- 범위 쿼리를 할 수 없다는 점이 유일한 단점
- 해시 샤드 키를 생성하기 위해서는 우선 해시 인덱스를 생성한 뒤 컬렉션을 샤딩하면 됨
- 존재하지 않는 컬렉션에 해시 샤드 키를 생성하려 하면 shardCollection은 사용자가 고르게 분산된 청크를 원한다고 가정해, 즉시 한 무더기의 빈 청크를 생성해서 클러스터에 분산함
- shardCollection을 실행한 직후, 각 샤드는 클러스터에 키 범위가 균등하게 분산된 N개의 청크를 갖는다고 출력함
- 컬렉션에는 아직 도큐먼트가 존재하지 않지만, 도큐먼트를 입력하기 시작하면 쓰기는 처음부터 샤드 간에 균등하게 분산됨
- 일반적으로 다른 샤드에 쓰기를 시작하려면 청크가 커져서 나뉘고 옮겨 가기를 기다려야 하지만 이러한 자동 처리 과정을 통해 모든 샤드는 즉시 청크 범위를 갖게 됨
3.2 GridFS를 위한 해시 샤드 키
- 일반적으로 GridFS 컬렉션은 방대한 양의 파일 데이터를 포함하므로 샤딩 후보로 적합하지만 fs.chunks에 자동으로 생성된 두 가지 인덱스는 그다지 좋은 샤드 키가 아님
- {"_id": 1}은 오름차순 키이고, {"files_id": 1, "n": 1}은 fs.files의 "_id" 필드를 가지므로 역시 오름차순 키
- 하지만 "files_id" 필드에 해시 인덱스를 생성하면 각 파일은 클러스터에 무작위로 분산되고, 하나의 파일은 항상 하나의 청크에 포함됨
- 쓰기는 모든 샤드에 균등하게 이뤄지고 파일 데이터 읽기는 단 하나의 샤드에서 수행되기 때문에 읽기와 쓰기 모두에 최선의 방법
- 이를 설정하려면 {"files_id": "hashed"}에 새로운 인덱스를 생성해야 함
3.3 파이어호스 전략
- 다른 서버들보다 좀 더 강력한 서버가 있다면, 덜 강력한 서버보다 더 많은 부하를 다루게 할 수 있음
- i.g. 샤드 하나가 다른 장비의 10배에 이르는 부하가 다룰 수 있고 운좋게 샤드도 10개 더 있다고 가정했을 때 모든 입력이 더 강력한 샤드로 가도록 강제할 수 있고, 밸런서는 오래된 청크를 다른 샤드로 보낼 수 있음
- 이는 쓰기의 응답 대기 시간을 줄임
- 이 전략을 사용하려면 최상위 청크를 더 강력한 샤드에 고정해야 하는데 우선 이러한 샤드를 영역화해야 함
> sh.addShardToZone("<shard-name>", "10x")
- 이제 오름차순 키의 현재 값부터 무한대까지 샤드에 고정하는데 그러면 새로운 쓰기가 모두 해당 샤드로 감
- 아래 쿼리를 호출하면 모든 입력은 마지막 청크로 전달되고 항상 "10x"로 영역화된 샤드에 위치함
- 하지만 현재 값부터 무한대까지의 범위는 영역 범위를 수정하지 않는 한 해당 샤드에 갇히게 되는 문제점 발생
> sh.updateZoneKeyRange("<dbName.collNam>", {"_id": ObjectId()}, {"_id": MaxKey}, "10x")
- 위 문제를 해결하기 위해서는 다음과 같이 하루에 한 번씩 키 범위를 갱신하는 cron 잡을 설정 해야 함
- 이제 이전 날짜의 모든 청크를 다른 샤드로 옮길 수 있음
- 해당 전략의 또 다른 단점은 확장을 위해 다소 변경이 필요하다는 점
- 갖아 강력한 서버가 유입되는 쓰기 분량을 더는 처리하지 못한다면, 부하를 다른 서버와 나눌 방법은 전혀 없음
- 파이어호스를 끼워 넣을 고성능 서버가 없거나 영역 샤딩을 사용하지 않는다면, 오름차순 키를 샤드 키로 사용하지 않는 것을 권장
- 오름차순 키를 사용하면 모든 쓰기가 하나의 샤드로 몰리게 됨
3.4 다중 핫스팟
- 몽고DB에서 대규모 데이터 쓰기 성능과 분산을 최적화하는 것은 매우 중요한 과제인데 특히 오름차순으로 증가하는 값을 샤드 키로 사용할 때, 특정 서버에만 데이터가 몰리는 '핫스팟(hotspot)' 문제가 생길 수 있음
- 몽고DB의 독립 실행형(mongod) 서버는 오름차순으로 데이터를 쓸 때 가장 효율적으로 동작하지만 클러스터 전체에 데이터를 고르게 분산하려면, 무작위성 또는 다양한 값이 샤드 키에 포함되어야 함
- 이 두 가지 요구 사항이 충돌할 때 다중 핫스팟 전략을 사용하게 됨
- 첫 번째 필드: 카디널리티 (서로 다른 값의 개수)가 낮은, 대략적으로 임의의 값을 가지며 해당 값은 전체 데이터를 여러 '더미'(청크)로 나누는 역할을 하며, 완벽하게 균등하지는 않지만 시간이 지나 입력 데이터가 많아질수록 클러스터 전체에 점점 더 고르게 분산됨
- 두 번째 필드: 오름차순으로 증가하는 값이며 청크 내부에서는 해당 값이 항상 증가하는 특성을 이용해, 각 샤드에서의 쓰기 효율성을 극대화시킴
데이터 분할 및 분산 방식
- 샤드 키의 첫 번째 부분을 기준으로 여러 청크로 데이터를 나누는데 처음에는 각 임의의 값마다 하나의 청크가 생길 수 있고, 데이터가 많아지면 같은 임의의 값을 가진 복수의 청크로 확장됨
- 각 청크 내부는 두 번째 필드 (오름차순 값)에 따라 정렬되며 이로 인해 샤드 내부에서는 효율적인 순차적 쓰기가 가능하게 됨
- 샤드마다 하나의 청크만 있을 경우, 모든 샤드에 오름차순 쓰기가 고르게 분산되어 매우 효율적인 상태
확장성과 효율성의 한계
- 샤드 수(n)만큼의 '핫스팟' 청크가 있을 때는, 각 청크가 각 샤드에 분산되어 효율적
- 하지만 핫스팟 청크 수가 샤드 수에 비해 적으면 (즉, 일부 샤드에만 집중되면) 분산 효과가 떨어짐
- 반대로, 데이터가 계속 증가해서 핫스팟 청크가 너무 많아지면, 사실상 무작위 쓰기와 비슷한 상태가 되어 오름차순 쓰기의 장점이 사라짐
청크와 더미의 구조적 특징
- 각 청크는 오름차순 정렬된 도큐먼트의 '더미'로 볼 수 있음
- 각 샤드는 여러 개의 더미 (청크)를 가질 수 있고, 각 더미는 더 이상 분할될 때까지 지속적으로 커짐
- 청크가 분할되면, 새롭게 생성된 여러 청크 중 하나만이 계속해서 오름차순 쓰기를 받는 '핫스팟 청크'가 되며 나머지 청크들은 더 이상 커지지 않고, 사실상 '죽은 청크'가 됩니다 (데이터가 추가로 들어오지 않음).
- 더미(청크)가 여러 샤드에 고르게 분산되면, 전체 쓰기 부하 역시 고르게 분산됨
4. 샤드 키 규칙 및 지침
- 샤드 키를 선정하기 전에 알아야 할 실질적인 제약 조건이 몇 가지 있음
- 샤드 키를 결정하고 생성하는 작업은 인덱싱과 개념이 비슷하며 사실 샤드 키는 가장 자주 사용하는 인덱스
4.1 샤드 키 한계
- 값이 배열인 키가 있을 경우 sh.shardCollections()는 실패하며, 해당 필드에 배열을 입력하도록 허용되지 않으므로 샤드 키는 배열이 될 수 없음
- 도큐먼트의 샤드 키 값은 입력 후 수정할 수 있지만 몽고DB 4.2 이전 버전에서는 도큐먼트의 샤드 키 값을 수정할 수 없었음
- 특이한 형태의 인덱스는 대부분 샤드 키로 사용할 수 없음
- 특히 위치 인덱스로는 샤딩할 수 없음
- 대신 해시 인덱스는 샤드 키로 사용 가능
4.2 샤드 키 카디널리티
- 샤드 키가 급격하게 증가하든 꾸준히 증가하든 상관없이, 고르게 입력될 값으로 키를 선택해야 함
- 인덱스와 마찬가지로 샤딩은 카디널리티가 높은 필드에 좀 더 효율적으로 작동함
- i.g. 값이 오직 `DEBUG`, `WARN`, `ERROR` 뿐인 `logLevel` 키가 있으면 몽고DB는 데이터를 세 개 이상의 청크로 쪼갤 수 없음
- 변화가 거의 없는 키를 샤드 키로 사용하려면, "logLevel"과 "timestamp"처럼 더욱 다양성 있는 키와 함께 복합 샤드 키를 생성해서 사용하는 것을 권장
- 키 조합의 카디널리티가 높으면 좋음
5. 데이터 분산 제어
- 자동 데이터 분산은 때때로 사용자의 요구사항과 맞지 않음
- 이 절에서는 샤드 키 선정 및 몽고DB의 자동분산을 넘어서는 몇 가지 방법을 살펴봄
5.1 다중 데이터베이스와 컬렉션을 위한 클러스터 사용
- 몽고DB는 컬렉션을 클러스터의 모든 샤드에 균등하게 분산하는데, 성질이 같은 데이터를 저장할 때는 잘 작동하지만 다른 데이터보다 `가치가 낮은` 로그 컬렉션이 있을 경우 비싼 서버에서 공간을 차지할 수도 있기 때문에 비효율적
- 강력한 샤드가 하나 있으면 실시간 컬렉션에만 사용하고, 나머지 컬렉션은 사용하지 못하게 할 수 있음
- 별도의 클러스터를 생성할 수도 있지만 몽고DB에 특정 데이터를 넣고 싶은 위치에 대한 구체적인 지식을 내릴 수 있음
- 설정하려면 셸에서 sh.addShardToZone() 보조자를 사용
- 이제 컬렉션마다 다른 샤드에 할당할 수 있음
- i.g. 실시간 컬렉션은 아래와 같으며 해당 컬렉션의 음의 무한대부터 무한대까지를 "high"로 태깅된 샤드에 저장
- super.important 컬렉션의 모든 데이터는 다른 서버에 저장되지 않으며 이는 다른 컬렉션을 분산하는 방법에 영향을 미치지 않음
- 다른 컬렉션은 여전히 해당 샤드 및 다른 샤드에 균등하게 분산됨
> sh.updateZoneKeyRange("super.important", {"<shardKey>": MinKey}, {"<shardKey>": MaxKey}, "high"}
- 품질이 낮은 서버에서 로그 컬렉션을 보관하는 데 비슷한 작업 수행
- 로그 컬렉션은 이제 shard0004와 shard0005에 균등하게 분할됨
> sh.updateZoneKeyRange("some.logs", {"<shardKey>": MinKey}, {"<shardKey>": MaxKey}, "low")
- 영역 키 범위를 컬렉션에 할당하고 나서 즉시 영향을 미치지는 않음
- 이 작업은 해당 샤드들이 컬렉션을 옮길 목적지라고 밸런서에 알려줌
- 따라서 로그 컬렉션 전체가 shard0002에 있거나 샤드 간에 균등하게 분산됐다면, 모든 청크가 shard0004와 shard0005로 이동하는 데 약간의 시간이 걸림
- 영역에서 키 범위를 제거하려면 sh.removeRangeFromZone()을 사용
- i.g. 범위는 이전에 네임스페이스 some.logs 및 특정 영역에 대해 정의한 범위와 정확히 일치하도록 지정해야 함
> sh.removeRangeFromZone("some.logs", {"<shardKey>": MinKey}, {"<shardKey>": MaxKey})
5.2 수동 샤딩
- 때때로 요구 사항이 복잡할 때나 특수한 상황에서는 데이터를 분산할 위치를 완전히 제어하고 싶을 수 있음
- 데이터가 자동으로 분산되지 않도록 밸런서를 끄고, moveChunk 명령을 통해 수동 데이터 분산 가능
- 밸런서를 끄려면 mongos에 접속해서 셸 보조자 sh.stopBalancer()를 사용해 밸런서를 비활성화시켜야 함
- 현재 이동이 진행 중이라면, 이동이 끝날 때까지는 아무런 효과가 나타나지 않으며 진행 중인 이동이 끝나면 ㄴ밸런서는 데이터를 이동하는 것을 멈춤
> sh.stopBalancer()
- 밸런서가 꺼지고 나면 데이터를 수동으로 옮길 수 있음
- 우선 config.chunks를 확인해서 어떤 청크가 어디에 위치하는지 찾아낼 수 있음
> db.chunks.find()
- 이제 moveChunk 명령으로 청크를 다른 샤드로 이동할 수 있으며 이동할 청크의 하한 범위를 명시하고 청크가 옮겨갈 샤드의 이름을 입력함
> sh.moveChunk("test.manual.stuff", {user_id: NubmerLong("...")}, "test-rs1")
- 여기까지 수동 샤딩 방법을 설명했지만 특별한 상황이 아니라면 자동 샤딩을 사용하는 것을 권장
- 예상치 않던 샤드에 핫스팟이 생기면 결국 해당 샤드에 대부분의 데이터가 들어가기 때문
- 특히 수동으로 독특한 분산을 하면서 동시에 밸런서를 실행하지 말 것을 권장
- 밸런서가 불균형한 수의 청크를 감지하면 컬렉션을 다시 균등하게 하려고 수동으로 작업한 것을 뒤섞을 위험이 있음
- 청크의 불균형한 분산을 원할 경우 앞서 설명한 `다중 데이터베이스와 컬렉션을 위한 클러스터 사용`에 설명한 영역 샤딩 기법을 사용하는 것을 권장
참고
몽고DB 완벽 가이드 3판 - 한빛미디어
반응형
'DB > 몽고DB 완벽 가이드 3판' 카테고리의 다른 글
[17장] 샤딩 관리 (0) | 2025.05.17 |
---|---|
[15장] 샤딩 구성 (0) | 2025.05.16 |
[14장] 샤딩 소개 (0) | 2025.05.16 |
[13장] 복제 셋 관리 (0) | 2025.04.26 |
[12장] 애플리케이션에서 복제 셋 연결 (0) | 2025.04.25 |