Skip to content

Latest commit

 

History

History
535 lines (333 loc) · 30.9 KB

File metadata and controls

535 lines (333 loc) · 30.9 KB

6. 샤딩

복제(레플리카) vs 샤딩 복제: 고가용성 샤딩: 분산처리

Shared Cluster 구축

  • Config Server : 메타 정보 저장
  • Mongos : 라우터 서버 ( for 데이터 조회, 저장)
  • Shard Server : 데이터 저장

6.1 Sharding 이란?

스케일 업 vs 스케일 아웃
  • 스케일 업 : cpu나 메모리 등의 서버 사양을 UP
  • 스케일 아웃 : 서버의 수 Up

6.1.1 필요성

  • Scale Up에는 한계.
  • 샤딩이 필요할 것 같으면 처음부터 샤딩 추천( 데이터 분산, 애플리케이션 코드 수정 비용)

6.1.2 샤딩 종류

  1. 수직 샤딩
    • 기능별로 컬렉션을 grouping, 그룹별로 샤드 할당.
    • 구현 간단.
    • 프로그램 변화 최소화
    • 샤드 간 부하 불균형 빈번 (자주 쓰는 컬렉션이 있는 샤드 부하가 더 클 수 있음.)
  2. 수평 샤딩
    • 하나의 컬렉션에 저장된 도큐먼트들을 영역별로 파티셔닝 -> 1/N 개씩 각 샤드가 나눠 가지는 방식
    • 샤딩의 기준 필드 선정 (Shard key) 중요.

보통 시스템 구조 흐름

  • 단일 샤드 -> 수직 샤딩 -> 수직 및 수평 샤딩

  • 컬렉션에 대한 샤딩 비활성화 -> 자동으로 수직 샤딩

  • 컬렉션에 대한 샤딩 활성화 -> 수평 샤딩

6.2 MongoDB 샤딩 아키텍쳐

  • Shard Server : 다수의 레플리카 셋으로 구성
  • Config Server : 하나의 샤드 클러스터에 단 하나의 레플리카 셋만 존재
  • Mongos(라우터) : 1개 이상.

6.2.1 샤드 클러스터 컴포넌트

  • 라우터
    • 사용자 쿼리요청 받아서 샤드에 전달하고 샤드로부터 결과를 받아 사용자에게 반환
  • 샤드 서버
    • 사용자 데이터 저장
  • 컨피그 서버
    • 스플릿에 관한 메타 정보
    • 데이터 밸런싱 작업
    • 샤딩 되지 않은 객체에 대한 정보는 없음 (각 샤드 서버가 로컬로 관리)

6.2.2 쿼리 수행 절차 (라우터 ~ Config 서버 통신)

과정
  1. 사용자 쿼리가 참조하는 컬렉션의 청크 메타정보를 가져와 (Config Server) 라우터 메모리에 캐시
  2. 사용자 쿼리 조건 분석
    • 조건에 샤딩 키 존재 -> 해당 샤딩 키가 포함된 청크 정보 검색해서 해당 샤드에만 요청
    • 조건에 샤딩 키 X -> 모든 서버에 요청
  3. 결과 병합하여 반환
Config 서버 요청 when?
  • 라우터에 청크 메타 정보 없을 경우

  • 메타 정보가 오래되어 샤드 서버에서 받은 데이터와 맞지 않을 때

    -> 컨피그 서버 부하는 적은 편

6.2.3 Config 서버

샤딩된 클러스터를 운영하는 데 있어 필요한 모든 정보 저장.

컨피그 서버에 직접 로그인 하여 확인 할 수도 있지만, 일반적으로 라우터를 이용해서 MongoDB 클러스터에 로그인한 다음 config 데이터베이스로 이동하면 필요한 데이터 조회할 수 있다.

Config 서버에서 가지고 있는 컬렉션
  • database

    • 샤드 클러스터가 가지고 있는 데이터베이스의 목록 관리
    • 샤딩 여부, 프라이머리 샤드가 어느 샤드인지
  • collections

    • 샤드 클러스터가 가지고 있는 컬렉션의 목록 관리
    • 샤딩되지 않은 컬렉션은 각 로컬 샤드 서버에 저장
    • 변경 시점, 삭제 된 컬렉션인지 여부, 샤딩 키, 샤딩 키가 유니키인지 여부
  • chunks

    • 샤딩된 컬렉션의 모든 청크 정보 관리
    • 청크가 포함된 컬렉션의 네임스페이스, 청크가 가지는 값의 시작, 끝 범위, 샤드 이름
  • shards

    • 모든 샤드 서버의 정보를 레플리카 셋 단위로 관리
    • host, tags(지역 기반 샤딩일 때 태그 목록)
  • mongos

    • 라우터는 매 30초마다 샤드 클러스터 모든 멤버와 ping 메시지를 통해 확인
    • 인스턴스가 종료되어도 삭제 되지 않음
    • 서버와 포트를 _id 필드에 저장, 실행 여부 및 핑 메시지 주고 받은 시점 저장
  • settings

    • 청크의 밸런싱과 관련된 작업의 설정 저장

      {"_id": "chunkSize", "value": 64} // 청크 사이즈 64MB
      {"_id": "balancer", "stopped": false} // 밸런서 작동 여부
      {"_id": "autosplit", "enabled": true} // 자동 청크스플릿 여부
      
  • version

    • 버전 정보
  • lockpings

    • 샤드 클러스터 각 메버와 컨피그 서버와의 연결 확인 정보
  • locks

    • 샤드 서버나 라우터 멤버들이 서로의 작업을 동기화하면서 처리할 때 사용하는 컬렉션
  • changelog

    • 컨피그 서버의 메타 정보변경을 유발한 이벤트에 관해 정보성 이력 관리

6.2.4 컨피그 서버의 복제방식

클러스터의 메타 정보는 사용자 데이터의 일관성 유지를 위한 매우 중요한 정보이므로 Config Server를 3대 이상으로 복제할 것을 권장.

  • 미러링 된 형태의 데이터 복제 방식 사용 (3.2 이전 버전)
  • 사용자 데이터와 동일하게 레플리카 셋으로 배포하는 방법 제공 (3.2 이상 버전)
6.2.4.1 SCSS(Sync Cluster Connection Config)

미러링 방식으로 config server의 데이터를 동기화 하는 방식으로 서버들을 각각 따로 설치하고 운영 프로그램(MongoDb서버 & 라우터서버)에서 모두 접속하여 각 서버의 데이터를 동기화 한다.

config 정보를 변경하고자 할 때, config server들에 접속하여 변경(UPDATE)하는 문장을 각각 실행하고 모두 성공적으로 완료되면 커밋을 수행하는 분산 트랜잭션을 실행하는 방식으로 처리된다.

이러한 분산 트랜잭션 처리 방식은 config server의 동기화 문제들을 자주 유발하는 원인이 된다. 잠금 테이블을 동기화하지 못해 라우터 서버가 청크 스플릿이나 밸런싱을 실행하지 못하는 경우 존재하고 이런 경우에는 수동으로 동기화 해야 한다. 정상 컨피그 서버의 데이터를 덤프해 비정상 서버에 적재 ( 데이터 날짜를 통해 최신 여부 판단 )

3.2 버전부터는 Deprecated, 3.4버전부터는 미러링 방식의 컨피그 서버 구성이 완전히 없어졌다.

6.2.4.2 CSRS(Config Server as Replaca Sets)

일반 사용자 데이터를 저장하는 샤드 서버처럼 레플리카 셋으로 구현함으로써 컨피그 서버와 샤드 서버의 구성방식이 같아졌다.

레플리카 셋으로 구축할 때에 다음과 같은 조건을 만족해야 한다.

  • 컨피그 서버는 WiredTiger 스토리지 엔진을 사용해야 한다. (must)
  • 레플리카 셋은 아비터를 가질 수 없다.
  • 레플리카 셋은 지연된 멤버를 가질 수 없다.
  • 최소 3개 이상의 멤버로 구성해야 한다. (권장 사항)

클라이언트는 컨피그 서버의 프라이머리 멤버로 접속하여 쿼리나 데이터 변경 명령을 실행한다.

6.2.5 컨피그 서버 가용성과 쿼리 실행

최소 3대 이상 투입 권장

미러링일 때는 1대 또는 3대만 허용하는데 이는 컨피그 서버 코드 상에 고정된 상숫값이다. 레플리카 셋 방식에서는 최소 3개 이상의 멤버를 권장하고 서버 1대가 죽을 때를 고려해 홀수 개로 맞춰야 한다. 모든 메타 정보 조회 및 변경 쿼리의 ReadConcern과 WriteConcern을 "majority"로 설정하는데, 이는 전체 레플리카 셋 멤버의 과반수에 접근할 수 있어야만 쿼리 수행 할 수 있다. 즉, 컨피그 서버가 2대일 경우 멤버 중 하나만 죽어도 메타 정보 조회 및 변경이 불가해진다.

  • 컨피그 서버의 데이터를 변경하는 경우
    • 청크 마이그레이션 실행 시
    • 청크 스플릿 실행 시
  • 컨피그 서버의 데이터를 조회하는 경우
    • 라우터 서버가 새로 시작되는 경우(혹은 재시작)
    • 컨피그 서버의 메타 데이터가 변경된 경우
    • 사용자 인증 처리 시

위와 같은 경우가 아니라면 컨피그 서버에 연결할 수 없더라도 사용자의 쿼리를 처리하는 데는 문제가 없다. 하지만, 컨피그 서버에 접속이 불가 하다면 새로운 커넥션 생성이 불가(기존 커넥션에서의 쿼리 실행은 가능) 하므로 장애로 연결될 수 있다. 그러니 가용상태를 유지하라!

6.2.6 라우터(mongos)

라우터 서버는 사용자의 쿼리 요청을 샤드 서버로 전달하고 샤드 서버로부터 쿼리 결과를 병합해서 다시 사용자에게 반환하는 프록시 역할을 수행한다.

  • 사용자 쿼리를 전달해야 할 샤드 서버 결정 및 해당 샤드로 쿼리 전송
  • 샤드 서버로부터 반환된 결과 조합하여 사용자에게 반환
  • 샤드간 청크 밸런싱 및 청크 스플릿 수행

라우터는 컨피그 서버로부터 클러스터의 메타 정보를 메모리에 캐시하고 있어서 사용자 쿼리를 어느 샤드 서버로 전달해야 할지 판단 가능하다.

라우터는 각 샤드가 내려준 결과를 바로 반환하는게 아니라 한 번의 필터링을 거친다. 청크 마이그레이션 중이거나 , 청크 마이그레이션 도중에 실패 했을 때에 샤드 키 기준에 맞지 않는 도큐먼트들을 샤드로부터 받아 올 수 있는데 이러한 도큐먼트들의 유효성을 체크해서 유효하지 않은 데이터는 버리고 나머지만 반환한다.

또한, 정렬이나 LIMIT 그리고 SKIP 같은 특정 쿼리 옵션에 대해서 특수한 처리를 수행하기도 한다. 정렬이 필요하지 않은 쿼리일 때는 각 샤드로부터의 결과를 라운드-로빈 방식으로 가져온 다음 반환한다.하지만 정렬이 필요할 대에는 라우터가 샤드 서버들 중에서 프라이머리 샤드를 결정하여 "$orderby" 옵션을 같이 쿼리에 전송한다. 그러면 프라이머리 샤드가 나머지 샤드로부터 결과를 받아 정렬 수행 후 최종 결과를 라우터 서버로 반환한다.

6.2.7 라우터의 쿼리분산

사용자의 쿼리에 샤딩 기준 키 값에 대한 조건을 가지고 있느냐에 따라 쿼리 요청이 달라진다.

6.2.7.1 타겟 쿼리(Targeted Query)

샤드 키 조건을 가진 사용자 쿼리에 대해 라우터는 조건을 분석해서 사용자 쿼리가 원하는 데이터가 있는 샤드서버(들)로만 요청한다.

6.2.7.2 브래드캐스트 쿼리(Broadcast Query)

샤드 키를 쿼리 조건으로 가지지 않는 경우에는 라우터가 작업 범위를 특정 샤드로 줄일 수가 없다. 이런 경우에는 모든 샤드로 쿼리를 요청해야 하고 모든 샤드로부터 반환된 결과들을 병합해서 사용자에게 반환해야 한다.

다음과 같은 쿼리는 샤드 키와 전혀 무관하게 항상 브로드캐스트 쿼리 방식으로만 처리된다.

  • 다중 업데이트(Multi-Update)
  • UpdateMany()와 DeleteMany() 업데이트 대상 검색 조건이 반드시 샤드 키를 모두 포함하는 경우에만 타겟 쿼리로 실행할 수 있다. 복합 필드를 샤드 키로 가지는 컬렉션에서는 샤드 키를 구성하는 모든 필드가 조건으로 사용되어야만 타겟쿼리로 실행 가능

6.2.8 라우터 배포

2.x 버전에서는 샤드로부터 전달 받은 결과를 정렬하는 작업도 일부 수행 3.x 버전에서부터는 정렬과 관련된 무거운 작업은 모두 샤드 서버로 옮겨졌다. 이는 라우터를 아주 가볍고 빠르게 구현하고자 하는 MongoDB의 방향성이 잘 보이는 부분이다.

중요성이 떨어지는 컴포넌트이다 보니 배포 방법에 대한 고민이 생긴다.

6.2.8.1 응용 프로그램 서버와 함께 라우터 배포

장점

  • 하나의 물리 장비에 장애가 나면 해당 장비만 작동 멈추고 나머지 서버들은 정상 작동

  • 응용 프로그램과 같은 물리 서버 위치 네트워크 비용 최소화 (로컬 커넥션)

단점

  • 응용프로그램 개수 증가와 함께 샤드서버들의 커넥션 증가
  • 라우터 서버만 죽을 경우 -> 살아있는 응용 프로그램에서의 요청은 어떻게 처리??
6.2.8.2 전용의 라우터 서버 배포

여러 개의 라우터 사용시 MongoDB 드라이버는 명시된 라우터 서버 중 적절한 서버 선택(개발 언어별 클라이언트 드라이버에 따라 동작 다름)

장점

  • 라우터 서버 수 감소
  • 샤드 서버와 맺어야 하는 커넥션 수 감소

단점

  • MongoDB에서 권장하지 않는 방식
  • 라우터 간 부하 분산 문제
  • 문제 발생시 라우터에 대한 요청 중지 및 되살아났을 때 요청 재 전달해야하는 등의 문제점
  • 자주 사용되지 않아서 버그 레포팅 적고 패치 가능성도 작다.
6.2.8.3 L4와 함께 라우터 배포

L4 스위치로 묶어서 라우터 사용

단점

  • L4 스위치를 거침으로써 네트워크 왕복 시간 증가

  • Find 쿼리와 후속의 GetMore 명령이 서로 다른 라우터로 요청되는 경우 발생

    MongoCursor<Document> cursor = coll.find(query).iterator();
    while(cursor.hasNext()) {
        Document obj = cursor.next(); // 자바 드라이버가 캐시한 결과가 없으면 커서 id를 이용해 라우터로 다시 결과 요청 -> 다른 라우터로 요청하게 되면 에러 발생
        ...
    }
6.2.8.4 샤드 서버나 컨피그 서버와 함께 라우터 배포

라우터는 CPU나 메모리 자원을 많이 사용하지 않기 때문에 크게 문제 되지 않지만 네트워크 사용량의 적절성에 대해서는 고려해야 한다. (RDBMS에 비해 레코드들이 큰 편)

6.2.9 커넥션 풀 관리

라우터는 MongoDB 드라이버(Client Driver)와 MongoDb 샤드 서버를 중계하는 역할로 클라/서버 쪽 커넥션을 모두 가지게 된다.

6.2.9.1 MongoDB 클라이언트

자바 드라이버를 이용한 MongoDB 서버 연결 예제

/* 단일 서버 접속 */
ServerAddress server = new ServerAddress("single-mongodb.com", 27017);
MongoClient mongoClient = new MongoClient(server);

/* 레플리카 셋 접속*/
// 레플리카 셋의 시드 리스트 준비
List<ServerAddress> seedList = new ArrayList<ServerAddress>();
seedList.add(new ServerAddress("rs-mongodb1.com", 27017));
seedList.add(new ServerAddress("rs-mongodb2.com", 27017));
// 인증 정보 설정
List<MongoCredential> credentials = new ArrayList<MongoClient>();
credentails.add(MongoCredential.createScramSha1Credential(username, DEFAULT_DB, password.toCharArray()));
// 접속 옵션 설정
MongoClientOptions options = MongoClientOptions.builder()
    .requireReplicaSetName(ReplSetName).build();
MongoClient client = new MongoClient(seedList, credentials, options);

/* MongoDB 라우터 접속*/
// 라우터 리스트 준비
List<ServerAddress> seedList = new ArrayList<ServerAddress>();
seedList.add(new ServerAddress("mongos1.com", 27017));
seedList.add(new ServerAddress("mongos2.com", 27017));
// 인증 정보 설정
List<MongoCredential> credentials = new ArrayList<MongoClient>();
credentails.add(MongoCredential.createScramSha1Credential(username, DEFAULT_DB, password.toCharArray()));
MongoClient client = new MongoClient(seedList, credentials, options);
  1. 주어진 서버(샤드서버일 수도, 라우터일 수도 있다.)로만 연결 생성
  2. 레플리카 셋 이름을 명시하지 않으면 최초 접속 시에 주어진 서버로만 접속하게 된다. 또한 몽고 서버가 자동 페일오버가 되었을 때 클라이언트 드라이버가 이를 인식하지 못할 수도 있다.
  3. 첫번째 방식이랑 동일한 방식이다. 다만 여러 서버를 나열하였기 때문에 쿼리를 서버로 전송할 때 주어진 서버들간의 부하 분산이나 페일 오버처리가 수행된다.
6.2.9.2 MongoDB 라우터 - MongoDB 샤드 서버

MongoDB 라우터는 클라이언트와의 커넥션과는 별개로 샤드 서버와의 연결을 맺는다. 즉, 클라이언트와의 커넥션이 많이 생성된다고 샤드와의 커넥션이 그만큼 많이 생성되지 않는다.

  • TaskExecutorPool : 서버의 CPU 코어 개수만큼 준비된다.
  • 커넥션 풀
  • 서브 커넥션 풀 : 하나의 커넥션 풀 내에 샤드 서버 개수만큼 생성.

갑작스럽게 요청이 많아질 경우 부족한 커넥션으로 인해 서버 지연 현상이 발생 가능.

커넥션 관련 옵션을 통해 최소, 최대 커넥션 개수를 지정할 수 있지만, 아직 라우터와 샤드서버 간의 커넥션을 충분히 생성할 수 있는 워밍업 방법은 여의치가 않은 상태이다.

6.3 샤딩 알고리즘

  • 레인지 샤딩
  • 해시 샤딩
  • 태그 기반 샤딩(Tag-Aware Sharding, 3.4 버전 이후로 지역기반 샤딩)

레인지, 해시 샤딩 : 데이터를 어떤 청크에 포함시킬지 결정

태그 기반 샤딩 : 위의 샤딩 알고리즘 + 청크의 분산 방식

6.3.1 청크(Chunk)

샤드 클러스터에서 각 컬렉션은 샤드 키를 기준으로 아주 잘게 쪼개져 여러 샤드에서 분산 관리 되고, 이 잘개 쪼개진 조각들을 청크라고 한다. 각 청크는 샤드 키의 원본 또는 해시 값의 일정범위(최대, 최소)를 가진다.

청크는 개별 데이터 파일(물리적)로 저장되지 않는다. 다만 논리적으로만 관리 되어지고 그 실체는 컨피그 서버에 메타 데이터로써 존재한다. 청크 검색 비용 X

기본적으로 64MB까지 커지고 그 이상 되면 밸런서에 의해 자동 스플릿된다.

6.3.2레인지 샤딩(Range Sharding)

샤드 키의 값을 기준으로 범위를 나누고, 사용자 데이터가 어느 청크에 포함될지 결정하는 샤딩 알고리즘이다. 레인지 샤딩은 단순히 각 청크가 어떤 범위의 값을 가지는지만 결정한다. 실제 각 청크가 어느 샤드 서버로 저장될지 결정하는 것은 레인지 샤딩의 범위에서 벗어난다.

샤드 키로 선정된 필드의 값을 변형하지 않고 비교 정렬한다.

mongo chunk� �� �미� ��결과

장점

  • 범위 검색 쿼리를 타겟 쿼리로 실행할 수 있다.

단점

  • 데이터가 균형있게 분산되지 않을 가능성이 높다.
    • 쪼갤 수 없을 정도가 되면 청크가 점보 청크가 되고 이러한 청크에 대해서는 관리 작업을 포기하게 된다.
  • 균등 분산 여부 (MongoDB 밸런서 -> 청크 개수 기준) 정확한 파악 어려움. ( 비정상 적으로 큰 청크 존재 가능)
  • 샤드 키의 값이 증가하는 방향인 경우 특정 청크에만 insert가 이루어질 수 있다.

샤드키 선정이 중요.!!!

6.3.3 해시 샤딩

샤드 키 값의 해시값을 이용해서 청크를 할당하는 샤딩 방식.(MD5 해시 사용)

샤드 키 값 -> 해시 -> 앞쪽 64bit 잘라서 64bit 정수형으로 사용한다. 그래서 해시 샤드키가 가질수 있는 값의 범위는 -2^64~ 2^63-1 이다.

실제로 해시 샤딩이라고 하지만 엄밀한 의미에서는 레인지 샤딩이다. 샤드 키의 해시 결과값으로 레인지 샤딩을 적용하는 형태이다. 이로 인해 비슷한 샤드 키값이라 하더라도 MD5 해시를 거침으로써 전체 범위에 대해 골고루 분산되는 효과를 가지게 된다.

레인지 샤딩의 단점 제거

  • 샤드 키 값이 특정 범위에 집중돼 있을 때 발생하는 데이터 불균형
  • 연속된 샤드 키 액세스로 인한 특정 샤드 서버의 부하 편중

그렇다 하더라도 샤드 키 값의 다양성이 떨어지면 해시 샤딩이라도 데이터가 특정 청크로 몰리는 현상은 피할 수 없다.

제약사항

  • 범위 검색 쿼리는 브로드캐스트 쿼리로 실행
  • 샤드 키 필드에 대해서 해시 인덱스를 생성해야 함. 해시 인덱스 제약사항
    • 단일 필드에 대해서만 해시 인덱스를 생성할 수 있음 (복합 인덱스에는 해시 인덱스 X)
    • 멀티 키 필드에 대해서는 해시 인덱스 생성 X (ex. 배열 필드)
    • 부동 소수점 필드는 소수점이하를 버리고 해시 함수 수행
    • 2^53보다 큰 부동 소수점에 대해서는 해시 인덱스 지원X

6.3.4 지역 기반 샤딩(Zone Sharding)

3.2 버전까지는 태그 기반 샤딩(Tag-Aware Sharding), 3.4버전부터는 지역 기반 샤딩(Zone Sharding)으로 이름이 변경 되었다. 기능상 변화가 있었던 것은 아니다.

지역 기반 샤딩은 독립적으로 사용할 수 있는 방식이 아니라 레인지 샤딩이나 해시 샤딩과 반드시 함께 사용해야 한다. 지역 기반 샤딩은 앞의 샤딩을 적용한 상태에서 데이터를 저장할 샤드를 한 번 더 조정할 수 있는 옵션이라고 이해할 수 있다.

  • 지역 기반으로 사용자 데이터의 구분
  • 특정 사용자 데이터를 지정된 샤드 서버로 구분해서 관리
  • 샤드 서버의 클래스 별로 저장할 데이터를 구분

6.3.5 샤드 키

샤드 키가 중요한 이유는 사용자 데이터를 여러 서버에 분산하는 방식을 결정하는 요소 이면서 한번 설정된 샤드 키는 변경 불가하기 때문이다.

샤드 키가 몽고 클러스터에 미치는 영향

  • 타겟 쿼리와 브로드캐스트 쿼리 결정
  • 각 샤드 서버의 부하 분산
  • 청크 밸런스 작업
6.3.5.1 타겟 쿼리와 브로드캐스트 쿼리 결정

대량의 데이터를 읽어가는 빈번하지 않은 쿼리의 경우에는 브로드캐스트 쿼리가 더 좋다(서버 부하 분산 효과 발생) 하지만 가벼우면서 빈번한 쿼리가 브로드캐스트일 경우에는 문제일 수 있다.

6.3.5.2 각 샤드 서버의 부하 분산

적절하지 못한 샤드 키는 각 샤드 서버가 가지는 데이터의 불균형을 초래할 수 있다.

6.3.5.3 청크 밸런스 작업

도큐먼트의 INSERT가 여러 샤드와 청크로 골고루 분산되어 저장될 수 있게 샤드키를 고려해야 한다. 그래야만 각 샤드가 가진 청크가 골고루 성장하게 되고 용량이 커진 청크가 스플릿된다 하더라도 가능한 한 밸런서가 샤드 간 청크 이동을 최소화 할 수 있도록 하는 것이 좋다.

6.4 프라이머리 샤드

프라이머리 샤드는 샤드 클러스터에서 샤딩되지 않은 컬렉션들을 저장하는 샤드를 의미하는데 레플리카 셋의 프라이머리와는 전혀 무관하다.

샤드 클러스터에서 처음 데이터베이스가 생성되면 샤드 중 데이터가 가장 적은 샤드를 선택해 생성되는 데이터베이스의 프라이머리 샤드로 설정한다. 샤딩되지 않은 컬렉션을 저장 할 때 어느 샤드에 저장할 지 결정해야 하는데 이때 기준이 되는 정보가 각 데이터베이스의 프라미어리 샤드다.

프라미어리 샤드는 이동이 가능하다. 특히 샤드를 제거할 때 해당 샤드에 있는 데이터를 다른 샤드로 이동시켜야 한다. 이 때 샤딩된 컬렉션만 해당 샤드에 있다면 sh.removeShard() 명령으로 데이터를 다른 샤드로 이동시키고 샤드를 제거할 수 있다. 하지만 샤딩되지 않은 컬렉션이 있다면 movePrimary 명령으로 프라이머리 샤드를 이동시키고 샤드를 제거한다. 이렇게 프라이머리 샤드의 이동중에는 데이터의 일관성 보장을 책임지지 않는다.

또한, 프라이머리 샤드가 이동된 후에 라우터에게 변경되었음을 알려야 한다. 이를 위해서는 다음의 두가지 방법이 있다.

  • 모든 MongoDB 라우터 재시작
  • 모든 MongoDB 라우터에서 flushRouterConfig 명령 실행 -> 컨피그 서버의 부하가 높아질 수 있음.

프라이머리 샤드 변경은 서비스에 많은 영향을 미치는 작업으로 가능하다면 샤딩 환경에서는 모든 컬렉션의 샤딩을 고려하자!

6.5 청크 밸런싱

데이터의 특성에 따라 각 샤드가 가진 데이터의 불균형은 언제든지 발생할 수 있다.

MongoDB에서는 최대한 각 샤드가 가진 청크의 개수를 동일하게 만들려고 노력하고 하나의 청크가 너무 비대해 지는 것을 막기 위해 청크에 데이터가 저장되거나 변경될 때마다 청크 스플릿 여부를 체크하는 작업을 수행한다.

6.5.1 샤드 클러스터 밸런서

밸런서는 각 샤드간 청크의 개수를 모니터링하다가 일정 수치 이상 차이가 나면(불균형) 적절히 청크를 옮겨서 샤드 간 부하의 균형을 맞춘다.

청크 이동이 시작되는 임계치가 존재하는 이유는 청크 이동의 비용이 크기 때문이다.

일단 청크 이동이 시작되면 청크 개수 차이가 기준치 이하로 떨어질 때까지 이동 분산시킨다. 이렇게 밸런서가 청크 불균형 감지 -> 청크 이동 시작 및 종료 까지의 주기를 밸런싱 라운드(Balancing Round)라고 부른다. 3.2 버전까지는 라우터에서 밸런서가 실행 되었는데 다수의 라우터 인스턴스들이 청크 이동을 수행하지 않도록 동기화하는 작업이 있는데 이로 인해 수많은 라우터가 청크 이동을위해서 경합하는 아키텍처로 구성이 되었다. 그래서 3.4버전부터는 밸런서 프로세스를 컨피그 서버에서 수행하도록 하였다.

청크 밸런싱으로 인한 청크 이동은 사용자 쿼리 처리 시간에 영향을 미칠 수 있다. 그래서 밸런싱이 특정 시간대에만 실행되도록 제어할 수 있는 기능이 있다.

mongos> db.settings.update(
	{_id: "balancer"},
	{$set: {activeWindow: {start : "22:00", stop : "04:00"}}},
	{upsert: true}
)

6.5.2 청크 스플릿

몽고디비의 청크는 자동 또는 수동으로 스플릿될 수 있는데, 자동 스플릿은 청크에 Document가 insert되거나 update 될 때에만 해당 청크를 스플릿할지 결정한다.

  • 컨피그 서버의 settings 컬렉션에서 balancer에 설정된 청크 사이즈보다 현재 청크의 크기가 클 때
  • 청크 이동의 최대 도큐먼트 건수보다 많은 도큐먼트를 현재 청크가 가지고 있을 때

기본적으로 청크는 64MB 정도의 데이터 조각으로 유지하려고 한다. 이 크기보다 커지면 청크를 쪼개는 작업을 진행하는데 스플릿은 splitVectorsplitChunk 두 단계로 나뉘어 처리된다.

  • splitVector : 특정 청크에서 스플릿이 필요한지 확인하고 스플릿이 필요하다면 스플릿을 실행할 위치의 배열을 리턴한다.
  • splitChunk : 위에서 리턴한 값이 스플릿할 수 있는 유효한 포인터를 포함하고 있다면 해당 지점을 기준으로 청크를 스플릿하도록 splitChunk 명령이 실행된다. splitChunk 작업은 청크 메타 정보를 새로운 청크 정보로 대체하는 것이 전부이다.

자동으로 스플릿 하지 못할 경우 관리자가 수동으로 스플릿 할 수 있다. 이 때 옵션에 따라 용도와 스플릿 결과가 달라질 수 있다.

  • find 옵션 : 주어진 조건을 이용해 특정 청크를 찾은 다음 그 청크를 크기가 같은 2개의 청크로 스플릿 조건에는 반드시 샤드 키의 조건을 포함해야 한다
  • middle 옵션 : 주어진 조건을 이용해 특정 청크를 찾아 2개의 청크로 스플릿한다. find와는 다르게 주어진 샤드 키 값을 기준으로 청크가 스플릿 된다. 해시 샤딩에서는 사용 불가
  • bounds 옵션 : 해시 샤딩을 사용하는 컬렉션을 스플릿하기 위한 명령. 해시 된 샤드 키값을 범위로 사용.

6.5.3 청크 머지

사용자의 데이터가 변경되는 과정에서 빈 청크가 생기기도 한다. 혹은 미리 청크 스플릿을 해 두었는데 데이터가 의도한 대로 저장되지 않아 빈 청크로 유지될 수도 있다. 이런 경우 서버 간의 부하가 균등하게 유지되기 어렵다. 이럴 때에 빈 청크를 주위의 연속된 청크와 병합할 수 있도록 mergeChunks 명령을 제공한다.

제약 사항
  • 병합하고자 하는 청크는 같은 샤드에 존재해야 한다.
  • 병합되는 청크 중에서 최소한 하나의 청크는 비어있어야 한다.
  • 연속된 샤드 키 범위의 청크만 병합할 수 있다.

6.5.4 청크 이동

  • 밸런서 : From-shard로부터 moveChunk 명령 실행
  • From-Shard : 이동 대상 청크의 상태와 moveChunk의 파라미터 오류 체크
  • F : To-shard에게 From-shard로부터 청크를 복사하도록 지시
  • To-shard : F와 인덱스를 비교한 다음 필요하다면 인덱스 생성
  • T : F 로부터 청크 데이터를 가져와 저장
  • T : 이 전 단계까지 실행하는 동안 변경된 데이터를 F로부터 가져와서 저장
  • T : Steady 상태
  • F : 컨피그 서버의 청크 메타 데이터 변경
  • F : T 로 이동된 청크의 데이터 삭제 (2.6 부터는 로컬 디스크에 백업)

6.6 샤딩으로 인한 제약

6.6.1 트랜잭션

일반적인 트랜잭션 속성 ACID(Atomicity, Consistency, Isolation, Durability)

  • MongoDB 단일 도큐먼트에 대한 변경(INSERT,UPDATE,DELETE)은 모두 트랜잭션 지원(의도적으로 데이터변경을 롤백 할 수 있는 방법은 없다.)
  • 4 점대 버전에서는 멀티 도큐먼트 트랜잭션 지원
  • WiredTiger는 트랜잭션이 지원이 가능한 스토리지 엔진이지만 샤딩으로 인해 데이터 셋이 분산되므로 분산 트랜잭션을 지원하지 않는 이상 트랜잭션은 큰 의미가 없다.

6.6.2 샤딩과 유니크 인덱스

샤딩된 컬렉션에서 유니크 인덱스는 샤드 키를 포함하는 인덱스에 대해서만 적용할 수 있다.

4.6.2.1 프라이머리 키의 중복 체크 처리

모든 컬렉션은 "_id" 필드는 프라이머리 키 필드이며 무조건 유니크해야 한다.

_id 는 UUID와 같이 서버, 프로세스 아이디 그리고 시간 정보를 이용해 중복되지 않는 값이 할당된다. 하지만 사용자가 직접 선택한 값을 저장할 때에는 사용자가 직접 그 유니크함을 보장해야 한다. 실제로 샤드 내에서는 중복 체크를 하지만 전체 샤드에 대해서는 중복 체크를 수행하지 않는다.

6.6.2.2 세컨드리 키의 중복 체크 처리

6.6.3 조인과 그래프 쿼리

$lookup(조인), $graphLookup(계층형 재귀 쿼리, 그래프 데이터 쿼리) 스테이지는 라우터가 아닌 샤드 서버에서 실행되는데 샤드 서버는 데이터 처를 위해 다른 샤드의 데이터를 참조할 수 없으므로 위의 쿼리들은 샤딩 환경에서 사용할 수 없다.