이 글은 아주 유명한 책인 <데이터중심애플리케이션설계>의 6장을 읽고 작성하였습니다. 따라서 내용 해석이 틀린 부분이 있을 수 있는데, 그런 경우 코멘트로 지적해주시면 감사하겠습니다.
파티셔닝이란?
- 데이터베이스의 분할
-
즉, 큰 테이블이나 인덱스를, 관리하기 쉬운 파티션이라는 작은 단위로 쪼개는 것
- partition은 몽고DB, ES에서는 shard라고 부른다.
- 샤딩이라고도 하지만, 보편적인 용어 차원에서 파티셔닝이라고 한다.
파티셔닝을 왜 하는 건데?
- 관리 용이성, 성능, 가용성 등의 향상을 노린다!
-
그 중에서도 가장 주된 이유는 확장성이다. 각 노드에서 각 파티션에 해당하는 질의를 독립적으로 실행할 수 있으므로 노드를 추가하면 처리량을 늘릴 수 있게 된다.
- k8s에서 pod이 하나 더 뜨는 상황과 비슷한 효과의 느낌..
파티셔닝과 복제(리플리케이션)
- 보통 파티셔닝과 리플리케이션은 함께 적용한다.
-
리플리케이션이 됨에 따라 특정 파티션이 마치 여러 쌍둥이 파티션처럼 복제되어 여러 노드에 나뉘어 저장된다.
- 참고로, 보통 한 노드에서 리더 파티션만 몰빵해서 가지고 있지 않고, 리더 파티션과 팔로워 파티션을 고루 가지고 있다.
파티셔닝의 기준을 어떻게 삼을 것인가?
-
전제조건: “데이터의 질의 부하를 노드 사이에 고르게 분산시키고 싶어서 파티셔닝을 한다.”
- 즉, 1명이 일하다가 9명을 더 채용해서 10명이 되었다면 10배의 일을 할 수 있어야 하는데, 여전히 1명(=핫스팟)만 일하고 9명이 놀면 안된다.(= skewed되면 안된다)
- 아무렇게나 랜덤으로(규칙없이) 분산시키면 안되나? 그 방법이 write할 땐 제일 쉽지만 read할 땐 어느 노드에 있는지 찾기가 어려워질 테니 좋은 방법은 아니다.
[참고] 본격 들어가기 전에 용어 정리
- Skewed(쏠림): skewed되어있으면 파티셔닝 효과가 매우 떨어져서 병목이 된다.
- Hot spots(핫스팟): 불균형하게 부하가 높은 파티션을 핫스팟이라고 한다.
파티셔닝 기준 잡는 전략1: key의 range를 기준으로 잡기
- 몽고로 치면 레인지 샤딩
- 도서관에는 책의 초성의 범위를 기준으로 책들이 꽂혀있다.
- 순서대로 정렬되어있으므로 지역성을 가지므로 범위 스캔이 쉬워지는 장점이 있다.
- 증가하는 값(ex: timestamp)을 key로 range 파티셔닝을 하면 마지막 파티션이 핫스팟이 될 수 있으니 주의해야 한다.
파티셔닝 기준 잡는 전략2: key의 hash값을 기준으로 잡기
- 몽고의 해시 샤딩
- 해시함수를 돌려서 쏠림을 확률적으로 차단한다.
-
범위 질의를 효율적으로 할 수 있는 range 파티셔닝의 장점을 잃는다. 해시함수를 통과하면서 지역성을 잃어버리기 때문에.
- [TMI] 카산드라는 이를 보완하기 위해 복합 키의 첫번째 부분만 해싱해놓고 남은 컬럼은 range로 두는 방식의 index를 사용한다고 한다.
[참고] 쏠린 작업부하와 핫스팟 완화
- 해시 파티셔닝을 하면 핫스팟을 줄이겠지만 원천 차단은 불가하다. 동일한 key에 폭발적인 요청이 들어오면 어쨌든 부하는 한쪽으로 쏠리기 때문.
- 핫스판 완화를 위한 가장 간단한 방법: 키의 시작이나 끝에 임의의 숫자를 붙이는 것. (hash salt를 의미하는 것 같음)
4. 파티셔닝과 세컨더리 인덱스
document에 의한 세컨더리 인덱스
- 예컨대 자동차 판매 사이트 관련해서 각 자동차들이 도큐먼트 형태로 데이터가 저장되어있고 도큐먼트id에 의해 파티셔닝 되어있다고 치자. 빨간색이라는 속성으로 자동차를 쿼리하고 싶다면? 색상에 대한 인덱스(세컨더리 인덱스)가 필요하게 된다.
- 이 각각의 세컨더리 인덱스는 각 파티션에서 각자 가지는 도큐먼트를 대상으로만 존재한다. 따라서 local index라고도 한다.
- 즉, 파티셔닝은 도큐먼트id만을 기준으로 하다보니 빨간색 자동차가 여러 파티션에 퍼져 있을 수 있는 상황이다.
- 이 경우 단점이, 빨간색 자동차를 찾고 싶을 때 모든 파티션에 쿼리해서 결과를 취합해야한다. 이를
scatter/gather
라고 한다. (이 경우, 몽고에서 쿼리플랜 즉 explain을 했을 때 SHARD_MERGE라고 나옴. https://oss.navercorp.com/shopping/passion-fruit/issues/2007#issuecomment-6757246)
term에 의한 세컨더리 인덱스
- 위 예시의 상황에서, 도큐먼트id가 아니라 색상을 기준으로 파티셔닝 하고 global index를 유지하는 방법이다. 그럼 읽기가 효율적이다.
- 하지만 전역 인덱스다보니 쓰는 과정에서 인덱스 반영 시간이 지연될 수 있다.
파티션 리밸런싱
- DB 가용량을 높이고자 하거나 장비 교체가 필요한 등의 사유로 리소스(노드)에 변화가 생기면 기존에 있던 데이터들을 다시 나눠가져야 하는데, 그걸 리밸런싱이라 한다.
리밸런싱 전략1: 나머지법(mod N) - 쓰면 안되는 방법
N이 바뀌면 또 다시 전체 리밸런싱이 필요해지므로 부적절하다.
리밸런싱 전략2: 파티션 개수를 고정하는 방법
노드 사이 데이터 분배를 파티션 단위로 하되, 좀더 복잡도를 낮추기 위해 파티션수까지 고정시켜버리자는 것이다. (단, 파티션수 고정이 어려운 비즈니스적 특성의 DB라면 이 방법은 쓰지 못한다.)
리밸런싱 전략3: 동적 파티셔닝
데이터 용량에 맞게 그때그때 파티션 개수와 크기가 정해지는 것이다. (통폐합도 되고, 분할도 되고.. 단, 정해진 최소 크기, 최대 크기는 있음)
리밸런싱 전략4: 노드 비례 파티셔닝
파티션 개수를 그냥 고정하는 게 아니라, ‘노드 당 파티션 개수’를 고정하는 방법이다. (마치 Kafka에서 브로커 증설을 하고나서 파티션을 늘린 뒤, 각 브로커에 파티션 리어사인을 하는 느낌과 비슷하다.)
클라이언트 요청에 대한 라우팅
여러 노드에 파티셔닝이 되어있을 때, 클라이언트에서는 요청을 어느 노드에 보내야할지(= 어느 노드의 ip를 쿼리의 접속정보로 삼을지) 어떻게 알 수 있을까?
대표적으로 3가지 방법이 있겠다.
- 일단 아무 노드에나 (round robin으로) 접속해서, 그 노드가 내가 찾던 노드가 맞다면 좋고 아님 말고. 없으면 다른 노드로 요청이 전달된다.
- 라우팅 계층이 로드밸런서 역할을 해서, 노드를 찾아준다.
- 애초에 클라이언트가 어느 노드에 요청할지 알고 그 타겟 노드로 곧바로 요청한다.
주키퍼란?
- 노드가 추가되거나 삭제되는 등 변동사항이 생기면, 그걸 라우팅 계층에 알려서 라우팅 정보를 최신으로 유지시키는 역할을 하는 녀석을 주키퍼라 한다.
- [TMI] Kafka 등은 주키퍼를 쓰는데, 몽고DB는 몽고스(mongos)라는 라우터계층이 자체적인 컨피그서버(config server)에 의존한다.
[참고자료]
마틴 클레프만(2018), 데이터 중심 애플리케이션 설계, 위키북스