파티셔닝

데이터셋이 매우 크거나 질의 처리량이 매우 높다면 복제만으로는 부족하고, 데이터를 파티션으로 쪼갤 필요가 있다. 이 작업을 샤딩이라고 한다.

데이터를 파티셔닝 하는 주된 이유는 확장성이다.

파티셔닝과 복제

파티셔닝을 해도, 파티션 된 단위를 기준으로는 복제를 할 수 있다. 한 노드에 여러 파티션을 복제 할 수도 있어서, 노드 하나에 특정 파티션의 리더와 나머지 파티션의 복제를 가지는식으로 교차해 두기도 한다.

키-값 데이터 파티셔닝

파티셔닝의 목적은 데이터와 질의 부하를 분산시키는 것이다. 이것이 달성되지 않았을 때 쏠렸다(skewed) 라고 한다. 이러한 쏠림이 발생하면, 특정 파티션에만 부하가 몰리게 되고, 다른 파티션은 거의 사용되지 않는 병목이 발생한다. 그 때 몰린 파티션을 핫스팟 이라고 한다.

이게 싫어서 지금부터 전략을 세우는데, 가장 쉬운 방법은 랜덤하게 파티션을 나누는 것이다.

그러면 데이터 분산은 매우 고르게 되지만, 데이터를 읽으려면 모든 파티션을 읽어야 하기 때문에, 이는 비효율적이다.

키 범위 기준 파티셔닝

그게 싫으면, 키 범위를 기준으로 파티션을 나누는 방법이 있다. 키를 특정 기준으로 정렬하는 방법은 앞에서 많이 다뤘고, 키값이 정렬되어있다면, 범위 기준으로 파티션을 나누는 것은 매우 쉽다. 심지어 각각의 범위를 재조정하는 것도 명확하다. 문제는 논리적인 정렬을 의존한다는 것인데, 만약 타임스탬프를 키로 사용한다면, 특정 날짜 혹신 시간대를 기준으로 유휴노드가 생기고 몰림이 발생할 수 있다.

키의 해시값 기준 파티셔닝

이런 논리적인 정렬을 피하기 위해서 해시값을 사용하는 방법이 있다. 해시값을 사용하면, 키의 해시값을 기준으로 파티션을 나누게 되고, 이는 랜덤하게 나누는 것과 같은 효과를 가진다. 또한, 해시값을 사용하면, 키의 범위가 어떻게 되든, 해시값은 항상 고르게 분포되기 때문에, 데이터를 고르게 분산시킬 수 있다. 그리고 간단한 해시함수를 사용하는 것으로도 충분하다. 물론 여기서도 문제가 생기는데, 바로 범위 기반의 질의에 약하다는 것이다. 범위와 같은 논리적인 내용에서는 인접해야할 키들이 다른 노드에 분산되어 있고, 결과적으로는 병렬적인 질의를 진행해야 한다.

쏠린 작업부하와 핫스팟 완화

해시를 해서 노드를 분산해도 핫스팟이 발생할 수 있다. sns의 유명 게시물이나, 특정 키워드로 검색된 결과물이나, 특정 사용자의 데이터와 같이, 특정 키에 대한 부하가 몰리는 경우가 있다. 이런경우 어플리케이션 로직으로 보완하는 경우도 있다. 예를 들어 특정 키에 대한 부하가 몰리면, 해당 키에 추가적인 식별값을 붙여서 해시를 분산하는 것이다. 물론 읽거나 다음 쓰기 등에 어플리케이션에서 처리해줘야 할 부분이 생기니 신중하게 필요한 부분만 적용해야 한다.

파티셔닝과 보조 인덱스

지금까지 설명한 파티셔닝 방식은 키-값 데이터 모델에 의존한다. 레코드를 기본키를 통해서만 접근하다면 키로부터 파티션을 결정하고 이를 사용해 해당 키를 담당하는 파티션으로 읽기 쓰기 요청을 전달 할 수 있다. 보조 인덱스가 연관되면 상황은 복잡해진다. 보조 인덱스 는 보통 레코드를 유일하게 식별하는 용도가 아니라 특정한 값이 발생한 항목을 검색하는 수단이다. 사용자 123이 실행한 액션을 모두 찾거나 빨간색 자동차를 모두 찾는 등의 작업에 쓰인다.

문서 기준 보조 인덱스 파티셔닝

뭔가 보조인덱스를 기준으로 파티셔닝을 하는 것처럼 보이지만, 사실 그건 아니고, 기본키와 같은 것들로 파티셔닝을 해두고 해당 노드에서 보조인덱스를 유지하는 것이다. 즉 쓰기 등에서 파티션된 노드의 보조인덱스만 업데이트 하는 방식이다. 다만 내가 오해한 것처럼 특정 보조인덱스를 기준으로 파티셔닝을 하는것은 아니라서, 병렬적인 요청과 취합이 필요하다. 이걸 스캐터/게더라고 하고, 꼬리지연시간이 발생할 수 있다. 그래서 전역 인덱스가 아닌 로컬 인덱스로 불리는 것이다.

용어 기준 보조 인덱스 파티셔닝

이건 반대로 전역인덱스를 두는 방식이다. 그리고 전역 보조 인덱스대로 파티셔닝까지 진행한다. 예를들어 색상이라는 보조인덱스가 잇다면, 노드별로 특정 색상을 담당해서 파티셔닝을 한다. 전역적으로 인덱스가 유지되기 때문에 읽기가 효율적이다.(모든 노드에 질의하고 취합하는 과정이 필요없다, 어디에 질의해야할지 알 수 있기 때문이다.) 반대로는 쓰기가 느려질 수 있다는 점이다.

파티션 재균형화

증설, 장애, 등의 이유로 파티션을 재배치해야할 때가 있다. 그리고 이러한 재배치를 진행 할 때, 최소한 만족해야하는 기준이 있다.

  1. 재균형화 이수 부하가 클러스터 내의 노드에 균등하게 분배되어야 한다.
  2. 재균형화 도중에도 데이터베이스는 읽기 쓰기 요청을 계속 처리해야 한다.
  3. 재균형화가 빨리 실행되고 네트워크와 디스크 부하를 최소하 할 수 있도록 데이터가 필요 이상으로 옮겨져서는 안된다.

파티션 개수 고정

가장 간단한 해결책이다, 파티션 갯수를 훨씬 널널하게 만들고, 노드들이 나눠가지게 하는 것이다. 노드가 추가되거나 삭제 될 때, 파티션 단위로 이동시키는 것이다.
이 방법은 간단하고, 빠르게 실행되지만, 파티션의 갯수가 고정되어 있어야 한다. (이론적으로는 변경이 가능하지만, 실제로는 어렵다.) 또 파티션 갯수에도 트레이드오프가 있어, 너무 많아지면 관리 오버헤드가 발생하고, 또 너무 적으면 증설이나 재배치가 필요한 경우 대응이 어렵다.

동적 파티셔닝

말 그대로 동적으로 파티션을 추가하는 방법이다. 특정 임계값을 넘어가면, 파티션을 추가하고, 데이터를 재배치하는 방법이다. 이 방법은 파티션의 갯수를 유연하게 관리할 수 있지만, 재배치가 빈번하게 일어나는 경우, 네트워크와 디스크 부하가 발생할 수 있다. 또 처음 시작할 때 하나의 파티션으로 시작해야 하기도 한다.(사전 분할 방식으로 보완하기도 한다)

운영: 자동 재균형화와 수동 재균형화

자동은 편리하지만, 예기치 못한 일이 발생 할 수 있고, 수동은 더 많은 제어권을 가지지만, 더 많은 노력이 필요하다.

요청 라우팅

병렬 요청을 보내지 않는 한, 파티션을 나눴으면 어디로 요청을 보내야할지 알아야 한다. 이를 위해 라우팅을 하는 방법중 몇가지 접근법이 있다.

  1. 아무 노드나 선택 -> 해당 노드에 파티션이 있는지 확인 -> 없으면 올바른 노드를 지정해서 이동시킴
  2. 모든 요청을 라우팅 계층으로 보냄 -> 그리고 라우팅 계층에서 올바른 노드로 보냄
  3. 클라이언트가 이미 파티션을 알고 있도록

세가지 방법 모두 짐작가는 장단점이 있어 보인다. 다만 문제는 노드의 파티션 변경을 어떻게 알아야 하는지에 대한 문제가 있다.

결론은 주키퍼 같은 코디네이터를 사용하는 것과, 특정 프로토콜을 통해 노드의 파티션 관련 정보를 서로 전파하는 것이다.