데이터 중심 애플리케이션 설계 | 06장. 파티셔닝

발표자 : 박현도, 이승익

파티셔닝

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

파티션을 나눌 때는 보통 각 데이터 단위(레코드, 로우, 문서)가 하나의 파티션에 속하게 한다. 데이터베이스가 여러 파티션을 동시에 건드리는 연산을 지원할 수도 있지만 결과적으로 각 파티션은 그 자체로 작은 데이터베이스가 된다.

즉 테이블은 논리적인 하나의 테이블이지만 물리적으로는 여러 개의 테이블로 분리해서 관리할 수 있게 해주는 기능이다.

파티션
출처 : Real MySQL 8.0

파티셔닝을 하는 주된 이유

  1. 확장성
  • 비공유 클러스터에서 다른 파티션은 다른 노드에 저장될 수 있다. 따라서 대용량 데이터셋이 여러 디스크에 분산될 수 있고 질의 부하는 여러 프로세서에 분산될 수 있다.
  • 단일 파티션에 실행되는 질의를 생각해 보면 각 노드에서 자신의 파티션에 해당하는 질의를 독립적으로 실행할 수 있으므로 노드를 추가함으로써 질의 처리량을 늘릴 수 있다. 크고 복작한 질의는 훨씬 더 어렵기는 하지만 여러 노드에서 병렬 실행이 가능하다.
  1. 이력 데이터의 효율적인 관리
  • 불필요한 데이터 삭제 작업은 단순히 파티션을 추가하거나 삭제하는 방식으로 간단하고 빠르게 해결 가능
  • ex) 로그 데이터 - 단기간에 대량으로 누적되며 일정 기간이 지나면 쓸모가 없어지는 데이터이며 삭제하며 백업할 때 고부하 작업

파티셔닝과 복제

보통 복제와 파티셔닝을 함께 적용해 각 파티션의 복사본을 여러 노드에 저장한다. 각 레코드는 정확히 한 파티션에 속하더라도 이를 여러 다른 노드에 저장해서 내결합성을 보장할 수 있다는 의미이다.

한 노드에 여러 파티션을 저장할 수도 있다. 리더 팔로워 복제 모델을 사용한다면 파티셔닝과 복제의 조합은 아래와 같은 형태가 된다.

각 파티션의 리더는 하나의 노드에 할당되고 팔로워들은 다른 노드에 할당된다.

각 노드는 어떤 파티션에게는 리더이면서 다른 파티션에게는 팔로워가 될 수 있다

파티셔닝과 복제

키-값 데이터 파티셔닝

  • skewed(쏠림): 파티셔닝이 고르게 이뤄지지 않아 다른 파티션보다 데이터가 많거나 질의를 많이 받는 파티션이 있는 경우
  • hotspot(핫스팟) : 불균형하게 부하가 높은 파티션을 지칭하는 용어

키 범위 기준 파티셔닝

각 파티션에 연속된 범위의 키를 할당하는 것이다. 이를 통해 각 범위들 사이의 경계를 알 수 있고 어떤 키가 어떤 파티션에 속하는지 쉽게 찾을 수 있다. 또한, 어떤 파티션이 어느 노드에 할당됐는지 알면 적절한 노드로 요청을 직접 보낼 수 있다.
ex) 어떤 최솟값에서 최대값까지

CREATE TABLE employees (
    id INT NOT NULL,
    first_name VARCHAR(30),
    last_name VARCHAR(30),
    reg_date DATE NOT NULL DEFAULT '1970-01-01',
    ....
) PARTITION BY RANGE( YEAR(hired) ) (
  PARTITION p0 VALUES LESS THAN (2000),
  PARTITION p1 VALUES LESS THAN (2010),
  PARTITION p2 VALUES LESS THAN (2020),
  PARTITION p3 VALUES LESS THAN MAXVALUE
);

파티션 insert
출처 : Real MySQL 8.0

장점

  • 각 파티션 내에서는 키를 정렬된 순서로 저장할 수 있다. 이렇게 하면 범위 스캔이 쉬워지는 이점이 있고 키를 연쇄적 색인으로 간주해서 질의 하나로 관련 레코드 여러 개를 읽어 오는데 사용할 수 있다.
  • ex) 애플리케이션에서 reg_date 칼럼 date type을 키로 사용한다고 하자. 이 경우 범위 스캔이 유용하다. 범위 스캔을 써서 특정 년도의 모든 데이터를 쉽게 읽을 수 있다.

단점

  • 특정한 접근 패턴이 핫스팟을 유발한다.
  • reg_date가 키라면 파티션은 년(year) 범위에 대응된다. 예를 들어 1년의 데이터를 파티션 하나가 담당하는 식이다.
  • 데이터를 갱신할 때 마다 데이터베이스에 기록하므로 쓰기 연산이 모두 동일한 파티션(해당하는 year)으로 전달되어 해당 파티션만 과부하가 걸리고 나머지 파티션은 유휴 상태로 남을 수 있다.

키의 해시값 기준 파티셔닝

해시 파티션은 특정 해시 함수에 의해 레코드가 저장될 파티션을 결정하는 방법이다.

파티셔닝용 해시 함수는 암호적으로 강력할 필요는 없다. 예를 들어 카산드라와 몽고 DB는 MD5를 쓰고 볼드모트는 파울러 놀보(Fowler-Noll-Vo) 함수를 사용한다. MySQL은 표현식의 결과값을 파티션의 개수로 나눈 나머지로 저장될 파티션을 결정한다.

ex) 문자열을 입력으로 받는 32비트 해시 함수가 있다고 하자. 이 함수에 문자열을 넣으면 겉으로 보기에는 0과 2의 32승 -1 사이의 무작위 숫자를 반환한다. 입력 문자열이 거의 유사해도 해시값은 숫자 범위 내에서 균일하게 분산된다.

해시
그림 6-3. 키의 해시값 기준 파티셔닝

장점

  • 쏠림과 핫스팟의 위험을 줄여 쏠린 데이터를 입력으로 받아 균일하게 분산되게 한다.

단점

  • 범위 질의를 효율적으로 실행할 수 있는 키 범위 파티셔닝의 좋은 속성을 잃어버린다.

  • 전에는 인접했던 키들이 이제는 모든 파티션에 흩어져서 정렬 순서가 유지되지 않는다.

  • 몽고 DB에서는 해시 기반 샤딩 모드를 활성화하면 범위 질의가 모든 파티션에 전송돼야 한다. 리악, 카우치베이스, 볼드모트에서는 기본키에 대한 범위 질의가 지원되지 않는다.

Θ 카산드라
카산드라는 두 가지 파티셔닝 전략 사이에서 타협한다. 카산드라에서 테이블을 선언할 때 여러 칼럼을 포함하는 복합 기본키를 지정할 수 있다.
키의 첫 부분에만 해싱을 적용해 파티션 결정에 사용하고 남은 칼럼은 카산드라의 SS테이블에서 데이터를 정렬하는 연쇄된 색인으로 사용한다.
즉, 복합 키의 첫 번째 칼럼에 대해서는 값 범위로 검색하는 질의를 쓸 수 없지만 첫 번째 칼럼에 고정된 값을 지정하면 키의 다른 칼럼에 대해서는 범위 스캔을 효율적으로 실행할 수 있다.

ex) 소셜 미디어 사이트에서 사용자 한 명이 수정한 문서 여러 개를 올릴 수도 있다.

특정한 사용자가 어떤 시간 구간에서 수정한 모든 문서를 타임스탬프 순으로 정렬해서 읽어올 수 있다.
다른 사용자가 수정한 정보는 다른 파티션에 저장될 수도 있지만 한 사용자가 수정한 정보는 한 파티션 내에서 타임스탬프 순으로 정렬된 상태로 저장 된다.
기본키 (user_id, update_timestamp)

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

앞에서 설명한 대로 키를 해싱해서 파티션을 정하면 핫스팟을 줄이는 데 도움이 된다. 그렇지만 핫스팟을 완벽히 제거할 수는 없다. 항상 동일한 키를 읽고 쓰는 극단적인 상황에서는 모든 요청이 동일한 파티션으로 쏠리게 된다.
ex) 소셜 미디어 사이트에서 수백만 명의 팔로워를 거느린 유명인이 뭔가를 하면 후폭풍이 발생할 수 있다. 유명인이 실행한 작업 때문에 동일한 키에 막대한 양의 데이터를 기록해야 할 수도 있다.
(키는 아마도 유명인의 사용자 ID이거나 사람들이 댓글을 다는 액션의 ID). 동일한 ID의 해시값은 동일하므로 해싱은 아무런 도움이 되지 않는다.

책에서 제안한 해결책

  • 각키의 시작이나 끝에 임의의 숫자를 붙인다. 임의의 10진수 두 개만 붙이더라도 한 키에 대한 쓰기 작업이 100개의 다른 키로 균등하게 분산되고 그 키들은 다른 파티션으로 분산될 수 있다.
  • 그러나 다른 키에 쪼개서 쓰면 읽기를 실행할 때 추가적인 작업이 필요해진다. 100개의 키에 해당 하는 데이터를 읽어서 조합해야 하기 때문이다. 추가적으로 저장해야 할 정보도 있다.
  • 이 기법은 요청이 몰리는 소수의 키에만 적용하는게 타당하다. 쓰기 처리량이 낮은 대다수의 키에도 적용하면 불필요한 오버헤드가 생긴다. 따라서 어떤 키가 쪼개졌는지 추적할 방법도 있어야 한다.

파티셔닝과 보조 색인

기본적으로 레코드를 기본키를 통해서만 접근한다면 키로부터 파티션을 결정하고 이를 사용해 해당 키를 담당하는 파티션으로 읽기 쓰기 요청을 전달할 수 있다.
그러나 특정한 값이 발생한 항목을 검색하는 수단인 보조색인이 연관되면 복잡해 진다.
보조 색인은 파티션에 깔끔하게 대응되지 않는 문제점이 있기 때문이다.

문서 기준 보조 색인 파티셔닝

문서 기준 인덱스

각 파티션에 속하는 문서만 담당하며 완전히 독립적으로 동작하는 색인 방법을 지역 색인(Local Index)이라고 한다.
데이터베이스에 문서 추가, 삭제, 갱신 등의 쓰기 작업을 실행할 때는 쓰려고 하는 문서 ID를 포함하는 파티션만 다루면 된다. 다른 파티션에 어떤 데이터가 저장되는지 신경 쓰지 않는다.
ex) 중고차를 판매하는 웹사이트를 운영한다. 각 항목에는 Document ID라고 부르는 고유 ID가 있고 데이터베이스를 문서 ID기준으로 파티셔닝 한다. (Document ID 0 ~499 : 파티션0, Document ID 500 ~ 999: 파티션 1)

color와 make에 보조 색인을 만들어야 한다. 색인을 선언했다면 데이터베이스가 자동으로 색인 생성을 할 수 있다. 예를 들어 빨간색 자동차가 데이터베이스에 추가되면 데이터베이스 파티션은 자동으로 그것을 color:red 색인 항목에 해당하는 문서 ID 목록에 추가한다.

주의할 점

  • 문서 ID에 뭔가 특별한 작업을 하지 않는다면 특정한 색상이거나 특정한 제조사가 만든 자동차가 동일한 파티션에 저장되리라는 보장이 없다.
  • 위 이미지를 보면 빨간색 자동차는 파티션 0에도 있고 파티션 1에도 있다.
  • 따라서 빨간색 자동차를 찾고 싶다면 모든 파티션으로 질의를 보내서 얻은 결과를 모두 모아야 한다.

파티셔닝된 데이터베이스에 이런 식으로 질의를 보내는 방법을 스캐터/개더(scatter/gather)라고도 하는데 보조 색인을 써서 읽는 질의는 큰 비용이 들 수 있다.
여러 파티션에서 질의를 병렬 실행 하더라도 스캐터/개더는 꼬리 지연 시간 증폭이 발생하기 쉽다. 그럼에도 보조 색인을 문서 기준으로 파티셔닝하는 경우가 많다.
ex) 몽고 DB, 리악, 카산드라, 엘라스틱서치, 솔라클라우드, 볼트DB

MySQL의 경우는 파티션 테이블에서 인덱스는 전부 로컬 인덱스에 해당 한다. 파티션과 관계 없이 테이블 전체 단위로 글로벌하게 하나의 통합된 인덱스는 지원하지 않는다.
그래서 WHERE 조건에 일치하는 레코드를 정렬된 순서대로 읽으면서 우선순위 큐(Priority Queue)에 임시로 저장한다. 그리고 우선순위 큐에서 다시 필요한 순서대로 데이터를 가져간다.
결과적으로 파티션 테이블에서 인덱스 스캔을 통해 레코드를 읽을 때 MySQL 서버가 별도의 정렬 작업을 수행하지는 않는다. 일반 테이블의 인덱스 스캔터럼 결과를 바로 반환하지 않고 내부적으로 큐 처리가 한번 필요하다.

용어 기준 보조 색인 파티셔닝

용어 기준 인덱스

지역 색인과 다르게 모든 파티션의 데이터를 담당하는 색인을 전역 색인(Global Index)이라고 한다.
이러한 방법은 한 노드에만 색인을 저장할 경우 해당 노드가 병목이 되어 파티셔닝의 목적을 헤칠 수 있기 때문에 여러 노드에 나눠서 저장한다.

찾고자 하는 용어에 따라 색인의 파티션이 결정되므로 이런 식의 색인을 용어 기준으로 파티셔닝됐다(term-partitioned)고 한다. 여기서는 color:red가 용어의 예다.

ex) 모든 파티션에 있는 빨간색 자동차 정보는 색인에서 color:red 항목에 저장되지만 색깔 색인은 a부터 r까지의 글자로 시작하는 색깔은 파티션 0에, s부터 z까지의 글자로 시작하는 색깔은 파티션1에 저장되도록 파티셔닝 된다.

자동차 제조사 색인도 마찬가지로 파티셔닝 된다.

특이점

  • 이전처럼 색인을 파티셔닝할 때 용어 자체를 쓸 수도 있고 용어의 해시값을 사용할 수도 있다. 용어 자체로 파티셔닝하면 범위 스캔(이를테면 자동차의 판매 희망가 같은 숫자 속성에 적용되는)에 유용한 반면 용어의 해시값을 사용해 파티셔닝하면 부하가 좀 더 고르게 분산된다.

장점

  • 문서 파티셔닝 색인에 비해 전역(용어 파티셔닝) 색인이 갖는 이점은 읽기가 효율적이라는 것이다.
  • 클라이언트는 모든 파티션에 스개터/개더를 실행할 필요 없이 원하는 용어를 포함하는 파티션으로만 요청을 보내면 된다.

단점

  • 단일 문서를 쓸 때 해당 색인의 여러 파티션에 영향을 줄 수 있으며 쓰기가 느리고 복잡하다.
  • (문서에 있는 모든 용어가 다른 노드에 있는 다른 파티션에 속할 수도 있다.)

이상적으로 색인은 항상 최신 상태에 있고 데이터베이스에 기록된 모든 문서는 바로 색인에 반영되어야 한다.
하지만 용어 파티셔닝 색인을 사용할 때 그렇게 하려면 쓰기에 영향받는 모든 파티션에 걸친 분산 트랜잭션을 실행해야 하는데, 모든 데이터베이스에서 분산 트랜잭션을 지원하지는 않는다.

현실에서는 전역 보조 색인은 대개 비동기로 갱신된다.(즉 쓰기를 실행한 후 바로 색인을 읽으면 변경 사항이 색인에 반영되지 않았을 수도 있다.)
ex) 아마존 다이나모 DB는 정상적인 상황에서는 전역 보조 색인을 갱신하는 데 1초도 안 걸리지만 인프라에 결함이 생기면 반영 지연 시간이 더 길어질 수도 있다.

전역 용어 파티셔닝 색인의 다른 사용처로는 리악의 검색 기능과 오라클 데이터 웨어하우스가 있다. 오라클 데이터 웨어하우스는 지역 색인과 전역 색인 사이에서 선택할 수 있다.

Q) Local Index? Global Index? 어떤 것이 더 사용하기 좋은지?
A) 물론 서비스 구조와 여러 가지를 고려해야겠지만 일반적으로 파티션 특성상 특정 파티션에 해당하는 작업(추가, 삭제)를 많이 하기 때문에 해당 작업 대상 파티션의 인덱스에만 영향을 받는 로컬 인덱스를 많이 사용 한다. 글로벌 인덱스 같은 경우는 파티션에 해당하는 작업을 하는 경우 전체 적으로 다시 재조정 작업을 해줘야 되기 때문에 부하가 많이 간다.

파티션 재균형화

개요 데이터베이스에 물리 장비에 대한 변화 발생

  • CPU, RAM, DISK 추가
  • 새로운 노드 추가

노드 이동

재균형화(rebalancing)

  • 클러스터에서 한 노드가 담당하던 부하를 다른 노드로 옮기는 과정

재균형화 요구사항

  • 부하 균등
  • 순단 X
  • 데이터 이동 최소화

재균형화 전략

해시 값에 mod N(노드 개수) 연산

모드연산1

모드연산2

모드 N 방식의 문제점

  • 노드 개수 N 이 바뀌면 대부분의 키가 노드 사이에 옮겨져야 함 → 재균형화 비용 상승

정적 파티셔닝 (파티션 개수 고정)

파티션고정1

파티션고정2

  • 리악, 엘라스틱서치, 카우치베이스, 볼드모트 에서 사용됨
  • 전체 데이터셋의 크기 변동이 심하다면 적절한 파티션 개수를 정하기 어려움
  • 파티션이 너무 커지면 재균형화 및 장애 복구 비용이 큼
  • 적절한 크기를 정하는 것이 제일 Best, 하지만 어려움

동적 파티셔닝 리스팅개발팀 > 06. 파티셔닝 > 동적파티셔닝1.png

동적 파티셔닝1

동적 파티셔닝2

  • 파티션 개수가 전체 데이터 용량에 맞춰 조정됨 (크기를 제한하고 개수를 늘림)
  • 빈 데이터베이스의 경우 파티션 경계 설정 값에 도달하기 전에는 파티션이 1개임
    • HBase 와 MongoDB 는 빈 데이터베이스에 초기 파티션 집합을 설정할 수 있음 → 사전 분할 (pre-splitting)

정적 파티셔닝 & 동적 파티셔닝 특징 정리

  • 정적 파티셔닝 : 파티션 크기가 데이터 크기에 비례
  • 동적 파티셔닝 : 파티션 개수가 데이터 크기에 비례
  • 두가지 모두 노드와는 관련이 없음

노드 비례 파티셔닝

노드 비례 파티셔닝1

노드 비례 파티셔닝2

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

완전 자동 재균형화

  • 관리자의 개입이 전혀 없이 시스템이 자동으로 재균형화

완전 수동 재균형화

  • 관리자가 명시적으로 파티션을 노드에 할당하도록 설정하고 관리자가 재설정할 때만 파티션 할당이 변경됨

요청 라우팅

요청 라우팅

  • 파티션이 재균형화 되면서 노드에 할당되는 파티션이 바뀜 → 데이터 위치가 바뀐다는 의미
  • 파티션 할당 정보를 실시간으로 파악하고 있어야 하는 요구사항 발생

Service discovery

라우팅 결정을 내리는 구성요소의 위치에 따른 접근법 라우팅 결정을 내리는 구성요소 ? → 위치 정보

그림 6-7
그림 6-7. 요청을 올바른 노드로 라우팅하는 세 가지 다른 방법

핵심 정보를 관리하는 방법 모든 경우에 핵심 문제는 라우팅 결정을 내리는 구성요소가 노드에 할당된 파티션의 변경 사항을 어떻게 알 수 있는가임
많은 코디네이션 서비스가 존재함 → https://stackshare.io/service-discovery

그림 6-8
그림 6-8. 주키퍼를 사용해 파티션 할당 정보 추적하기

데이터베이스 별 파티션 할당 정보 접근법

방법 1. 개별 노드가 파티션 할당 정보 소유

  • 카산드라, 리악
  • 데이터베이스 노드에 복잡성을 더하지만 외부 코디네이션 서비스에 의존하지 않는 것은 장점

방법 2. 라우팅 계층이 파티션 할당 정보 소유

  • HBase, 솔라클라우드, 카프카, 몽고DB
  • 코디네이션 라이브러리 사용 또는 자체적인 설정 서버 사용 (몽고DB)

방법 3. 클라이언트가 파티션 할당 정보 소유

  • 제보 부탁드립니다.

병렬 질의 실행

  • 데이터의 대규모화 → 대규모 데이터 분석 및 질의가 중요함 → 클러스터형 데이터베이스
  • 대규모 병렬 처리(massively parallel processing, MPP) 지원
  • RDB 는 조인, 필터링, 그룹화, 집계 연산 등 복잡한 종류의 질의 지원
  • MPP 질의는 클러스터 내의 서로 다른 노드에서 병렬적으로 실행됨



최종 수정 : 2022-03-18