[몽고DB 완벽가이드] 샤드 키 선정

반응형
728x90
반응형

샤드키 선정

컬렉션을 샤딩할때 데이터 분할에 사용할 1~2개의 필드를 선택하는데, 이 필드를 샤드 키라고 한다. 컬렉션을 샤딩하고나서는 샤드 키를 변경할 수 없으므로 올바르게 선택하는 것이 중요하다. 좋은 샤드키를 선정하려면 샤드 키가 애플리케이션의 요청을 분산하는 방법과 작업량을 이해해야한다. 

 

1) 샤드를 얼마나 많이 늘릴 것인가?

샤드가 3개인 클러스터는 샤드가 천개의 클러스터보다 훨씬 유연하다. 클러스터가 점점 더 커질때, 쿼리가 모든 샤드를 방문해야하는 쿼리를 피하려면 거의 모든 쿼리가 샤드 키를 포함해야한다.

 

2) 읽기 혹은 쓰기 응답 대기 시간을 줄이려고 샤딩하는가?

쓰기 응답 대기 시간을 줄이는 일은 일반적으로 요청을 지리적으로 더 가까운 곳이나 더 좋은 장비로 보내는 작업과 관련 있다.

 

3) 읽기 혹은 쓰기 처리량을 늘리려고 샤딩하는가?

처리량은 클러스터가 동시에 처리할 수 있는 요청의 개수를 말한다. 처리량을 늘리는 일은 더 많은 병렬 처리 기능을 추가하고 클러스터에 요청을 균일하게 분산하는 작업과 관련 있다.

 

4) 시스템 리소스를 늘리려고 샤딩하는가?

데이터의 1 기가 바이트당 몽고DB에 더 많은 메모리를 제공하려고 하는가? 만약 그렇다면 작업 셋의 크기를 가능한 한 작게 유지할 필요가 있다.

 

정리하자면, 샤드키를 선정할때 고려해야할 사항은 아래와 같다.

 

  • 샤드 키가 필요한 타겟 쿼리를 제공하는가?
  • 샤드 키가 시스템의 처치량이나 응답 대기 시간을 의도한 대로 변경하는가?
  • 작은 작업 셋이 필요하면 샤드 키가 그것을 제공하는가?

 

 

샤딩 구상

데이터를 분할할 때는 오름차순, 무작위, 위치 기반 키를 가장 일반적으로 사용한다. 다른 형태의 키도 사용할 수 있지만, 대부분의 사용 사례는 이중 하나에 해당한다. 

 

오름차순 샤드 키

오름차순 샤드 키는 일반적으로 "date" 필드나 ObjectId처럼 시간에 따라 꾸준히 증가하는 것이면 무엇이든 된다. 자동 증가하는 프라이머리 키 또한 오름차순 샤드 키의 예다. 

 

ObjectId를 사용하는 컬렉션에 "_id"와 같은 오름차순 필드로 샤딩한다고 가정하자. "_id"로 샤딩하면 "_id" 범위의 청크로 분할된다.

 

무작위 분산 샤드 키

오름차순 키와 대조되는 무작위 분산 샤드 키가 있다. 사용자명, 이메일 주소, UUID 등 데이터셋에서 고유하지 않은 키는 모두 무작위 분산 샤드 키가 될 수 있다.  많은 데이터가 입력될때 데이터의 무작위성은 입력이 모든 청크에 고르게 이루어져야함을 의미한다. 

 

만약 1만개의 데이터를 넣는다면 쓰기가 무작위로 분산되므로, 발생할 수 있는 이동 횟수를 제한하면서 각 샤드가 거의 비슷한 비율로 커질 것이다. 유일한 단점은, 몽고DB가 메모리 크기를 넘어서는 데이터를 임의로 접근하는데 효율적이지 않다는 점이다. 만약 가용 메모리가 있거나 성능이 저하돼도 상관없다면 무작위 키는 부하를 분산시키는데 훌륭한 방법이다.

 

위치 기반 샤드 키

사용자 IP, 정도와 위도, 주소 등이 될 수 있다. 반드시 물리적 위치 필드와 관련될 필요는 없으며, "위치"는 데이터를 그룹화하는 추상적인 방법이다. 어떤 경우든 위치 기반 키는, 어떤 유사성을 갖는 도큐먼트가 해당 필드 기반의 범위에 포함되는 키다. 이는 데이터를 사용자와 가까운 곳에 두거나 관계있는 데이터를 디스크에 함께 보관하는데 편리하다. 몽고 DB는 영역 샤딩(zone sharding)을 이용해서 이를 관리한다. 

 

예를들어, IP 주소로 샤딩된 도큐먼트의 컬렉션이 있다고 가정하자. 

  • 56.*.*.* : shart0000
  • 17.*.*.* : shard0000, shard0002
  • 다른 IP는 어디든 상관없다.
> sh.addShardToZone("shard0000", "USPS")
> sh.addShardToZone("shard0000", "Apple")
> sh.addShardToZone("shard0002", "Apple")

 

규칙을 생성해보자.

> sh.updateZoneKeyRange("test.ips", {"ip" : "056.000.000.000"}, ...
    {"ip" : "057.000.000.000}, "USPS")

 

56.0.0.0 보다 크거나 같고 57.0.0.0보다 작은 IP는 모두 "USPS" 샤드 영역에 붙인다.

 

영역 키 범위에 포함되지 않는 청크는 평범하게 이동되며, 밸런서는 계속 청크를 여러 샤드에 고르게 분산하려고 한다.

 

 

샤드키 전략

해시 샤드 키

데이터를 가능한 한 빠르게 로드할 수 있다. 해시 샤드 키는 어떤 필드라도 무작위로 분산된다.

 

[단점]

해시 샤드 키로는 범위 쿼리를 할 수 없다. 범위 쿼리를 하지 않는다면 해시 샤드 키가 유용하다.

 

해시 샤드 키일때 컬렉션에는 아직 도큐먼트가 존재하지 않아도, 도큐먼트를 입력하기 시작하면 쓰기는 처음부터 샤드 간에 균등하게 분산된다. 일반적으로 다른 샤드에 쓰기를 시작하려면 청크가 커져서 나뉘고 옮겨 가기를 기다려야한다. 반면에 이러한 자동 처리 과정을 통해 모든 샤드는 즉시 청크 범위를 갖게된다. 

 

해시 샤드 키는 unique 옵션을 사용할 수 없고, 다른 샤드 키와 마찬가지로 배열 필드를 사용할 수 없으며, 부동소수점 값은 해싱 전에 정수고 절삭된다. (1 = 1.99999)

 

파이어호스 전략

다른 서버들보다 좀더 강력한 서버가 있다면, 이 서버에 더 많은 부하를 다루게 할 수도 있다.

 

[예시]

샤드 1개가 다른 장비의 10배에 이르는 부하를 다룰 수 있을 경우, 모든 입력이 더 강력한 샤드로 가도록 강제할 수 있고, 밸런서는 오래된 청크를 다른 샤드로 보낼 수 있다. 이는 쓰기의 응답 대기 시간을 줄인다.

 

이 전략을 사용하려면 최상위 청크를 더 강력한 샤드에 고정해야한다. 우선 이러한 샤드를 영역화하자.

> sh.addShardToZone("<shard-name>", "10x")

이제 오름차순 키의 현재 값부터 무한대까지 샤드에 고정한다. 그러면 새로운 쓰기가 모두 해당 샤드로 간다.

> sh.updateZoneKeyRange("<dbName.collName>", {"_id" : ObjectId()}, ...
   {"_id" : MaxKey}, "10x")

이제 모든 입력은 이 마지막 청크로 전달되고 항상 "10x"로 영역화된 샤드에 위치한다.

 

 

샤드 키 규칙 및 지침

샤드 키를 선정하기전에 알아야 할 실질적인 제약 조건이 몇가지 있다.

샤드 키를 결정하고 생성하는 작업은 인덱싱과 개념이 비슷하다. 

 

샤드 키 한계

샤드 키는 배열이 될 수 없다. 

값이 배열인 키가 있으면 sh.shardCollections()는 실패하며, 그 필드에 배열을 입력하도록 허용되지 않는다. 또한 위치 인덱스로는 샤딩할 수 없다. 해시 인덱스는 샤드키로 사용할 수 있다.

 

샤드 키 카디널리티

샤드 키가 급격하게 증가하든, 꾸준히 증가하든 상관없이, 고르게 입력될 값으로 키를 선택해야한다. 인덱스와 마찬가지로 샤딩은 카디널리티가 높은 필드에 좀더 효율적으로 작동한다.

 

[예시]

"DEBUG", "WARN", "ERROR" 뿐인 logLevel 키가 있으면 몽고DB는 데이터를 3개 이상의 청크로 쪼갤 수 없다. 변화가 거의 없는 키를 사용하려면 logLevel과 timestamp처럼 더욱 다양성 있는 키와 함께 복합 샤드 키를 생성해서 사용하자.

 

키 조합의 카디널리티가 높으면 좋다.

 

 

데이터 분산 제어

몽고DB는 컬렉션을 클러스터의 모든 샤드에 균등하게 분산하는데, 성질이 같은 데이터를 저장할때는 잘 작동한다. 하지만 다른 데이터보다 '가치가 낮은' 로그 컬렉션이 있으면, 비싼 서버에서 공간을 차지하지 않기를 원할 것이다. 몽고DB에 특정 데이터를 넣고 싶은 위치에 대한 구체적인 지시를 내릴 수 있다.

 

셸에서 아래와 같이 수행한다.

> sh.addShardToZone("shard0000", "high")
> // shard0001 - 영역없음
> // shard0002 - 영역없음
> // shard0003 - 영역없음
> sh.addShardToZone("shard0004", "low")
> sh.addShardToZone("shard0005", "low")

이제 컬렉션마다 다른 샤드에 할당할 수 있다. 

아래는 "이 컬렉션의 음의 무한대부터 무한대까지를 "high"로 태깅된 샤드에 저장한다." 라는 의미의 명령어다.

> sh.updateZoneKeyRange("super.importnat", {"<shardKey>" : MinKey}, 
   ... {"<shardKey>" : maxKey}, "high")

super.important 컬렉션의 모든 데이터는 다른 서버에 저장되지 않고, 이는 다른 컬렉션을 분산하는 방법에는 영향을 미치지 않는다. 다른 컬렉션은 여전히 이 샤드 및 다른 샤드에 균등하게 분산된다.

 

"high"로 영역화된 샤드를 제외하면 어떤 샤드로 가든 상관없는 컬렉션이 있을 수 있다. 이 경우 영역화하여 그룹을 만들 수 있다.

> sh.addShardToZone("shard0001", "whatever")
> sh.addShardToZone("shard0002", "whatever")
> sh.addShardToZone("shard0003", "whatever")
> sh.addShardToZone("shard0004", "whatever")
> sh.addShardToZone("shard0005", "whatever")

 

 

수동 샤딩

요구사항이 복잡할때나 특수한 상황에서는 데이터를 분산할 위치를 완전히 제어하고 싶을 수 있다. 데이터가 자동으로 분산되지 않도록 밸런서를 끄고, moveChunk 명령으로 수동으로 데이터를 분산하자.

 

밸런서를 끄려면 mongos에 접속해서 셸 보조자를 사용해 밸런서를 비활성화한다.

> sh.stopBalancer()

 

moveChunk 명령으로 청크를 다른 샤드로 이동한다. 이동할 청크의 하한 범위를 명시하고 청크가 옮겨갈 샤드의 이름을 입력한다.

> sh.moveChunk(
  ... "test.manual.stuff"
  ... {user_id: NumberLong("-1844674047370955160")},
  ... "test-rs1")

 

 

특수한 상황이 아니라면 수동으로 하지말고 몽고DB의 자동 샤딩을 사용하자. 특히, 수동으로 독특한 분산을 하면서 동시에 밸런서를 실행하지 말자. 밸런서가 불균형한 수의 청크를 감지하면 컬렉션을 다시 균등하게 하려고 수동으로 작업한 것을 뒤섞는다.

 

 

반응형

Designed by JB FACTORY