학습 목표 : 대용량 데이터셋을 파티셔닝하는 몇 가지 방법을 살펴보고 데이터 색인과 파티셔닝의 상호작용을 알아본다.
클러스터에 노드를 추가하거나 제거할 때 필요한 재균형화에 대해서도 알아본다.
데이터베이스가 어떻게 요청을 올바른 파티션에 전달하고 질의를 실행하는지 살펴본다.
파티션(=샤딩) : 데이터셋이 매우 크거나 질의 처리량이 매우 높다면 복제만으로는 부족하고 데이터를 쪼갠다. 이 때의 단위가 파티션.
데이터 파티셔닝의 주요 이유 : 확장성 → 공유 클러스터에서 다른 파티션은 다른 노드에 저장될 수 있다. 따라서 여러 디스크에 분산될 수 있고 질의 부하도 여러 프로세서에 분산.
1. 파티셔닝과 복제
복제 + 파티셔닝을 이용해서 각 파티션의 복사본을 여러 노드에 저장. (즉, 각 레코드는 정확히 한 파티션에 속하더라도 이를 여러 노드에 저장해서 내결함성을 보장할 수 있다는 뜻)
각 파티션의 리더는 하나의 노드에 할당되고 팔로워들은 다른 노드에 할당. 각 노드는 어떤 파티션에게는 리더이면서 다른 파티션의 팔로워가 될 수 있다.
키-값 데이터 파티셔닝
쏠렸다 = 파티셔닝이 고르게 이뤄지지 않아 다른 파티션보다 데이터가 많거나 질의를 많이 받는 파티션이 있다.
핫스팟을 회피하는 단순한 방법 : 레코드를 할당할 노드를 랜덤하게 선택. but 어떤 노드를 선택해서 기록했는지 모르기 때문에 모든 노드에서 병렬적으로 질의 실행해야 함. → 키-값 데이터 모델인 경우 정렬되어 있으면 찾기 쉬움.
키 범위 기준 파티셔닝
각 파티션에 연속된 범위의 키를 할당하고 각 범위들 사이의 경계를 알면 어떤 키가 어느 파티션에 속하는지 쉽게 찾을 수 있다.
키 범위 크기(파티션 경계)는 반드시 동일하지는 않음. 관리자가 수동으로 선택하거나 데이터베이스에서 자동으러 선택할 수 있음.
SS테이블과 LSM 트리에서 사용한 방식을 이용해서 각 파티션 내에서 키를 정렬된 순서로 저장할 수 있다 → 범위 스캔이 쉬워지고, 키를 연쇄된 색인으로 간주하면 질의 하나로 관련 레코드 여러 개를 읽어오는데 사용 가능.
단점 : 특정한 접근 패턴에서 핫스팟을 유발. ex) 타임스탬프가 키일 경우, 오늘 날짜에 해당하는 데이터 조회. 이 경우 쓰기 연산이 오늘 날짜에 해당하는 동일한 파티션으로 전달되어 해당 파티션만 과부하 / 나머지 파티션은 유휴 상태
보완 : 센서 데이터베이스라면, 타임스탬프 앞에 센서 이름을 붙여서 파티셔닝 시 센서 이름을 먼저 사용. → 하나의 시간 범위 내에서 여러 센서의 값을 얻고 싶으면 센서 이름마다 별개의 범위 질의를 실행해야 함.
키의 해시값 기준 파티셔닝
좋은 해시 함수를 이용해서 쏠린 데이터를 입력 받아 고르게 분산. but 프로그래밍 언어의 내장된 해시 함수는 같은 키를 넣어도 다른 프로세스에서는 다른 해시값을 반환할 수도 있으므로 적절하지 않을 수 있다.
- 키에 적합한 해시 함수를 구한다.
- 각 파티션에 키 범위 대신 해시값 범위를 할당한다.
- 파티션의 범위에 속하는 해시값을 가진 모든 키를 그 파티션에 할당.
: 일관성 해싱 (키를 파티션 사이에 균일하게 분산시킴. 파티션 경계의 크기가 동일하게 나눌 수 있고, 무작위에 가깝게 선택할 수도 있다) → 혼동하지 않게 해시 파티셔닝
단점 : 범위 질의를 효율적으로 실행할 수 있다는 장점 유실.
보완 : ex) 카산드라 → 복합 기본키 지정. 키의 첫 부분만 해싱에 적용해서 파티션 결정에 사용하고, 남은 칼럼은 SS테이블에서 정렬하는 연쇄된 색인으로 사용.
- 사용 사례 ) 기본키 = (user_id, update_timestamp) 인 경우, 한 사용자가 수정한 정보는 한 파티션 내에서 타임스탬프 순으로 정렬된 상태로 저장.
쏠린 작업부하와 핫스팟 완화
키를 해싱해서 파티션을 정하면 핫스팟 줄일 수 있지만 완벽히 제거는 어렵다. ex) 항상 동일한 키를 읽고 쓰는 상황 → 모든 요청이 동일한 파티션으로
데이터 시스템에서 자동으로 크게 쏠린 작업부하를 보정X → 애플리케이션 단에서 쏠림 완화 필요.
ex) 요청이 많이 쏠리는 키를 발견 시 각 키의 시작이나 끝에 임의의 숫자를 붙임 → (단) 읽기를 실행할 때 분산된 키에 해당하는 데이터를 읽어서 조합해야 함. + 추가적인 정보 저장 필요.
따라서 요청이 몰리는 소수의 키에만 적용하고 어떤 키가 쪼개졌는지 추적할 방법도 있어야 함.
2. 파티셔닝과 보조 색인
보조 색인이 연관되면 상황이 더 복잡해진다.
보조 색인 : 관계형 DB나 문서형, 검색 서버에서 자주 사용되기 때문에 (데이터 모델링에 매우 유용) 파티션에 깔끔하게 대응되지 않아도 다음의 두 가지 방법을 이용해서 사용한다.
문서 기준 보조 색인 파티셔닝
지역 색인 : DB에서 자동으로 색인을 생성하고 해당 파티션 내부에서 보조 색인으로 저장. 쓰기를 할 때마다 보조 색인 업데이트.
- 각 파티션이 완전히 독립적으로 동작. 자신의 보조 색인을 유지하고 그 파티션에 속하는 문서만 담당.
- 데이터베이스에 문서 추가, 삭제, 갱신 등의 쓰기 작업은 쓰려고 하는 문서ID를 포함하는 파티션만 다루면 됨.
- 보조 색인 기준으로 무언가를 찾고 싶다면 모든 파티션으로 질의를 보내서 얻은 결과를 모두 모아야 함 (= 스캐터/개더) → 높은 비용
- 그럼에도 사용하는 곳이 많음.
용어 기준 보조 색인 파티셔닝
전역 색인 : 모든 파티션의 데이터를 담당하는 전역 색인.
- 병목을 방지하기 위해서 한 노드에만 색인을 저장하지는 않음. 전역 색인도 파티셔닝이 필요.
- 찾고자 하는 용어에 따라 색인의 파티션이 결정되므로 용어 기준 파티셔닝(term-partitioned)라고 함. (용어 : 문서에 등장하는 모든 단어)
- 색인 파티셔닝 시 용어 자체를 쓸 수도, 해시 값을 사용할 수도 있음.
- 용어 자체 사용시 범위 질의에 용이
- 해시 값 사용 시 부하가 좀 더 고르게 분산
- 문서 파티셔닝 색인에 비해 읽기가 효율적 ← 스캐터/개더 불필요
- 쓰기가 느리고 복잡합 ← 하나의 쓰기가 해당 색인의 여러 파티션에 영향을 줄 수도 있음.
- 색인이 항상 최신 상태는 아닐 수 있음. (비동기 사용) ←쓰기에 영향받는 모든 파티션에 걸쳐서 분산 트랜잭션을 실행해야 하는데, 이를 지원하지 않는 DB들이 있음.
3. 파티션 재균형화
재균형화 : 클러스터에서 한 노드가 담당하던 부하를 다른 노드로 옮기는 과정
재균형화의 최소 요구 사항
- 재균형화 후, 부하가 클러스터 내에 있는 노드들 사이에 균등하게 분배
- 재균형화 도중에도 데이터베이스는 읽기 쓰기 요청을 받아들일 수 있어야 함
- 재균형화가 빨리 실행되고 네트워크와 디스크 I/O 부하를 최소화할 수 잇도록 노드들 사이에 데이터가 필요 이상으로 옮겨져서는 안 됨.
재균형화 전략
쓰면 안 되는 방법: 해시값에 모드 N 연산을 실행
모드 N 방식의 문제점 : 노드 개수 N이 바뀌면 대부분의 키가 노드 사이에 옮겨져야 한다. → 재균형화 비용이 너무 비쌈
파티션 개수 고정
파티션을 노드 대수보다 많이 만들고 각 노드에 여러 파티션을 할당. ex) 10대의 노드로 구성된 클러스터 + 파티션 1000개로 구성 시 각 노드마다 약 100개의 파티션 할당
- 클러스터에 노드 추가 : 새 노드는 파티션이 다시 균일하게 분배될 때까지 기존 노드에서 파티션 몇 개를 뺏어옴.
- 노드 제거 : 사라질 노드에서 파티션을 다시 뺏어옴.
- 노드 사이에 파티션은 통째로 이동. 파티션 개수나 키도 변하지 않음. 노드에 어떤 파티션이 할당되었는지만 변동. → 대량의 데이터 전송으로 시간 소요.
- 파티션 이동 중에 발생하는 읽기나 쓰기는 기존에 할당된 파티션을 이용.
- DB 처음 구축 시부터 파티션 개수가 고정 → 충분히 높은 값으로 선택해야 하지만 너무 큰 수를 선택해도 개별 파티션 관리 오버헤드가 존재한다.
- 전체 데이터셋의 크기 변동이 심하다면 적절한 파티션 개수를 정하기 어려움.
동적 파티셔닝
파티션 크기가 설정된 값을 넘어서면 파티션을 두 개로 쪼개 각각에 절반의 데이터가 포함되게 한다. 데이터가 많이 삭제되어 파티션 크기가 임곗값 아래로 떨어지면 인접한 파티션과 합친다. (B트리의 최상위 레벨 작업과 유사)
- 각 파티션은 노드 하나에 할당되고 각 노드는 여러 파티션을 담당할 수 있다.
- 큰 파티션이 쪼개진 후 부하의 균형을 위해 분할된 파티션 중 하나가 다른 노드로 이동될 수 있다.
- 파티션 개수가 전체 데이터 용량에 맞춰 조정된다.
- 빈 데이터베이스는 파티션 경계를 어디로 정해야 하는지에 대한 사전 정보가 없어서 시작할 때는 파티션이 한 개이다. (데이터셋이 작으면 모든 쓰기 요청이 하나의 노드에서 실행되고 나머지는 유휴 상태)
- 사전 분할 : 빈 데이터베이스에 초기 파티션 집합을 설정
- 키 범위 파티셔닝 뿐만 아니라 해시 파티셔닝에서도 사용 가능
노드 비례 파티셔닝
파티션 개수가 노드 대수에 비례하도록 노드당 할당되는 파티션 개수를 고정.
- 노드 대수가 변함 없는 동안 개별 파티션 크기가 데이터셋 크기에 비례해서 증가. 노드 대수가 올라가면 파티션 크기는 다시 작아진다.
- 새 노드가 추가되면 고정된 개수의 파티션을 무작위로 선택해 분할하고 각 분할된 파티션의 절반은 그대로 두고 다른 절반은 새 노드에 할당.
- 파티션 경계를 무작위로 선택하려면 해시 기반 파티셔닝 사용.
운영: 자동 재균형화와 수동 재균형화
- 완전 자동 재균형화 : 노드 하나의 과부하를 죽었다고 생각해서 부하를 옮기기 위해 재균형화 시도. → 과부화 걸린 노드 + 재균형화 작업으로 연쇄 장애 발생 가능.
- 완전 수동 재균형화
→ 중간 지점 : 파티션 할당을 제안하지만 반영하려면 관리자가 확정해야 함.
4. 요청 라우팅
클라이언트에서 요청을 보낼 때 어떤 노드에 접속해야 하는지 어떻게 알까?
파티션 재균형화 시 노드에 할당되는 파티션이 바뀐다.
→ 서비스 찾기 문제
해결방안 3가지
- 클라이언트가 아무 노드에나 접속하게 하고 해당 노드에 요청 내용이 있으면 처리. 아니면 요청을 올바른 노드로 전달해서 응답을 받고 클라이언트에게 응답을 전달
- 클라이언트의 모든 요청을 라우팅 계층으로 보냄. 라우팅 계층에서 각 요청을 처리할 노드를 알아내고 해당 노드로 요청 전달. (라우팅 계층에서는 어떤 요청도 처리하지 않음)
- 클라이언트가 파티셔닝 방법과 파티션이 할당된 노드를 알고 있어서 중개자 없이 올바른 노드로 직접 접속 가능.
주키퍼(ZooKeeper) by. 링크드인의 에스프레소, HBase, 솔라클라우드, 카프카
- 각 노드는 주키퍼에 자신을 등록
- 주키퍼는 파티션과 노드 사이의 신뢰성 있는 할당 정보를 관리
- 라우팅 계층이나 파티션 인지 클라이언트같은 요소들은 주키퍼에 있는 정보를 구독.
- 파티션 소유자가 바뀌거나 노드의 추가 삭제 시 주키퍼는 라우팅 계층에 이를 알려서 라우팅 정보를 최신으로 유지
가십 프로토콜(gossip protocol) by.카산드라, 리악
- 클러스터 상태 변화를 노드 사이에 퍼트린다.
- 아무 노드나 요청을 받을 수 있고 요청을 받은 노드는 올바른 노드로 요청을 전달.
- 노드에 복잡성을 더하지만 주키퍼 같은 외부 코디네이션 서비스에 의존하지 않음.
카우치베이스 : 재균형화는 수동으로, 라우팅 계층인 목시(moxi) 사용
병렬 질의 실행
단일 키를 읽거나 쓰는 간단한 질의 말고 분석용으로 사용되는 대규모 병렬 처리(massively parallel processing, MPP) 관계형 데이터베이스 → 더 복잡한 종류의 질의를 지원.
ex) 조인, 필터링, 그룹화, 집계 연산 등
MPP 질의 최적화기 : 복잡한 질의를 여러 실행 단계와 파티션으로 분해. 데이터베이스 클러스터 내의 서로 다른 노드에서 병렬적으로 실행될 수 있음.
'TIL > 데이터 엔지니어링' 카테고리의 다른 글
데이터 중심 어플리케이션 설계 - 9장. 일관성과 합의 (0) | 2023.07.28 |
---|---|
데이터 중심 어플리케이션 설계 - 5장. 복제 (1) | 2023.05.30 |
데이터 중심 어플리케이션 설계 - 4장. 부호화와 발전 (0) | 2023.05.22 |
데이터 중심 어플리케이션 설계 - 3장. 저장소와 검색 (0) | 2023.05.15 |
데이터 파이프라인 저장소 (0) | 2023.05.04 |
댓글