DB/몽고DB 완벽 가이드 3판

[16장] 샤드 키 선정

꾸준함. 2025. 5. 16. 15:31

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