9.1.1 샤딩이란

  • 데이터의 크기가 커지고 애플리케이션에서 읽기와 쓰기 처리량이 늘어남에 따라 현재의 서버로는 충분치 않은 상황이 올 수도 있다.(램이나 CPU 코어가 충분치 않을 수도있다.)
  • 대이터 크기가 늘어남에 따라 하나의 디스크나 RAID(https://ko.wikipedia.org/wiki/RAID) 어레이에 대량의 데이터를 저장하고 백업하는 것이 현실적으로 불가능해질 수도있다.
  • 데이터 베이스를 위해 현재 가용한 서버나 가상 하드웨어를 계속해서 사용하려면 하나 이상의 서버에 데이터베이스를 분산해야하는데 이것이 샤딩을 통해 이루어진다.

  • 상당수의 대규모 웹 애플리케이션은 여러개의 MySQL 데이터베이스에 걸쳐서 부하를 분산하는 _수동적인 샤딩_을 구현한다.
    • 수동적인 샤딩의 수행 : 데이터베이스를 룩업 데이터베이스로 지정함으로써 수행할 수 있다. 예 ) 매우 방대한 레코드를 가지고 있는 유저 테이블을 어러 개의 데이터베이스에 분산하였다고 가정하였을때 룩업데이터베이스는 각 유저 ID 혹은 일정 범위에 속하는 유저 ID를 해당 샤드에 매핑하는 메타데이터를 갖는다. 이로써 유저에 대한 쿼리는 두쿼리로 이루어진다 첫번째 유저의 샤드 위치를 얻기위해 룩업데이터베이스를 참조 두번째 유저 데이터를 가지고 있는 개별샤드에 대해 수행된다.
    • 수동적인 샤딩의 문제점 1 : 부하문제는 해결하지만, 데이터를 옮기는 것이 어렵다 하나의 샤드에 과부하가 걸리면 데이터를 다른 샤드로 옮기는 것이 수작업으로 이루어진다. ( 하나의 샤드에 과부하가 걸리면 데이터를 다른 샤드로 옮기는 것이 수작업으로 이루어진다. )
    • 수동적인 샤딩의 문제점 2 : 읽기와 쓰기를 라우팅하고, 전체적으로 데이터베이스를 관리하는 애플리케이션 코드를 작성하고 유지하기가 쉽지 않다는 점이다.
  • 즉 수동으로 데이터 베이스 샤딩을 해본 사람이라면 누구나 말하듯이 이것을 제대로 하기가 쉽지 않다.
  • MongoDB는 많은 부분에서 이 문제를 해결하기 위해 만들어졌다.
  • MongoDB는 샤딩 전과 후에 애플리케이션에 대한 인터페이스가 같도록 설계되어있다. 이것은 데이터베이스가 샤드 구조로 변경되더라도 애플리케이션 코드를 수정할 필요가 거의 없다는 점을 의미한다.

샤딩을 해야 할 때

  • 인덱스와 작업중인 데이터를 램에 유지하는 것이다. 애플리케이션의 데이터가 점점 제한없이 커진다면 그 데이터가 더 이상 램에 수용될 수 없는 시점이 온다. 즉 어떤 서버도 무한대의 램을 가질 수는 없는 법 따라서 결국에는 필요하게 된다.
  • 어떤 경우이건 기존의 시스템을 샤딩하고자 하는 결정은 디스크 활동, 시스템 부하, 그리고 언제나 중요한 데이터 대 램의 비율을 정기적으로 분석한 결과를 항상 바탕을 두어야한다

9.1.2 샤딩 작동 방식

샤딩 구성요소

  • page 255 그림 참조
  • 샤드 : MongoDB 샤드 클러스터는 데이터를 하나 혹은 그 이상의 샤드에 걸쳐서 분산 저장한다. 각 샤드는 MongoDB의 복제 셋으로 클러스터의 전체 데이터의 일부분을 저장한다. 각 샤드는 그 자체로 복제셋이기 때문에 자신만의 복제 방식을 가지고 있고 자동으로 장애조치를 할 수 있다. 복제셋에서처럼 개별 샤드에 직접 연결할 수는 있으나, 샤드 클러스터의 일부분이기 때문에 클러스터 전체 데이터의 일부만 액세스 할 수있다.
  • MONGOS 라이터 : 각 샤드가 클러스터의 전체 데이터의 일부만을 가지고 있다면 클러스터 전체에 대한 인터페이스가 필요. mongos가 바로 이일을 수행한다. 모든 읽기와 쓰기 요청을 해당 샤드에 보내는 라우터다.
    • mongos는 지속성이 없는 경량프로세스다. 애플리케이션은 로컬 mongos에 연결하고 mongos는 각 샤드에 대한 연결을 관리한다.
  • 설정 서버 : ### mongos 프로세스가 지속성이 없기 때문에 샤드 클러스터의 상태를 어디선가는 유지하고 있어야하는데, 이것이 설정 서버가 하는일이다. 설정 서버는 샤드 클러스터의 메타데이터를 지속적으로 유지한다. 이 데이터는 글로벌 클러스터의 설정사항과 각 데이터베이스, 컬렉션, 각 범위의 데이터의 위치, 그리고 샤드 간 데이터의 전송 내역을 가지고 있는 변동사항 로그다.
    • 설정서버가 가지고 있는 메타데이터는 클러스터가 제대로 동작하고 유지되기 위해 반드시 필요하다. 예 ) mongos 프로세스는 시작할 때마다 설정 서버로부터 메타데이터 복사본을 가지고 온다. 따라서 설정 데이터가 없으면 샤드 클러스터에 대한 일관된 관점을 가질 수 없기에 반드시 필요하다.
    • 그림을 살펴보면 3개의 설정 서버가 있지만 복제셋으로 구성되지 않은 것을 알수있다.mongos 프로세스가 쓰기를 할 때 2단계 커밋을 사용하는데, 이것을 통해 설정 서버들 사이의 일관성이 보장된다( 일관성 보장을 위한 각각의 설정서버들에게 커밋 ). 실제 서비스 시스템에는 정확히 3개의 설정 서버를 실행해야하고 중복성을 위해 서로 다른 별개의 서버에 호스트해야한다.

샤딩의 핵심 동작

  • 상황 : 스프레드 시트 관리를 위한 클라우드 기반 오피스 제품군을 개발하고 모든 데이터를 MongoDB에 저장한다고 가정해보자(google doc), 사용자는 원하는 대로 문서를 생성할 수 있고, 각 문서는 MongoDB의 spredsheet이라는 컬렉션 내의 한 도큐먼트로 저장된다. 시간이 지남에 따라 애플리케이션의 사용자가 백만여명이 되었다고 가정하였을때 이제 users와 spreadsheets라는 2개의 컬렉션을 생각해보자 users 컬렉션은 관리 할 만하다. 백만여명의 사용자라 할지라도 각 유저 도큐먼트당 1kb 만 잡아도 컬렉션은 대략 1GB이고, 하나의 서버로 쉽게 서비스할 수 있다. 하지만 spreadsheets 컬렉션의 경우 한사용자가 평균적으로 50개의 스프레드시트를 갖고 이 스프레디 시트의 평균 크기가 50kb라면, spreadsheets컬렉션의 크기는 1TB가 된다.
  • 이 애플리케이션이 매우 활발하게 사용된다면 모든 데이터가 램에 수용되길 바랄것이다. 이 데이터를 램안에 가지고 있고 읽기와 쓰기를 분산하기 위해서는 컬렉션을 샤드해야만한다. MongoDB가 이런 상황에 대한 해결책을 제공

컬렉션애 대한 샤딩

  • MongoDB의 샤딩은 범위 기반이다. 샤드된 컬렉션에서 모든 도큐먼트는 해당 키에 대해 어느 범위의 값에 속해야한다는 것을 의미
  • MongoDB는 이러한 범위 내에서 각 도큐먼트를 찾기 위해 이른바 샤드 키 를 사용한다.
) 컬렉션
{
_id : ObjectId("2g3g5j1h2v2g3h21j1j2j3")
filename : "spreadsheet-1",
updated_at : ISODate("2016-01-01T19:22:42:842Z"),
username : "jihoon",
data: "raw document data"
}
  • 샤드하기위해서는 필드중에 하나를 샤드키로 선언해야한다.
    • _id를 선택한다면 도큐먼트는 오브젝트 id의 범위를 기준으로 분산된다. 하지만 여기서는 id와 username으로 된 복합 샤드키를 선택할 것인데 이유는 잠시후에, 이제 각 범위는 유저네임의 범위가 된다.
  • 청크(chunk) : 청크는 하나의 샤드에 있는 샤드 키 값의 연속적인 범위다. 예) docs 컬렉션에서 샤드 데이터가 A와 B라는 두 샤드에 청크로 나뉘어져 있다면 아래 표와 같게 된다.
시작 샤드
- ∞ abbot B
abbot dayton A
dayton harris B
harris norris A
norris B
  • 각 개별 청크가 데이터의 연속적인 범위를 나타내긴 하지만 이 범위들은 어느 샤드에도 나타날 수 있다는 점이다.
  • “harris”에서 시작해서 “norris”로 끝나는 청크가 A라는 샤드에 존재한다는 말은, 이 범위 안에 들어가는 샤드 키를 갖는 모든 도큐먼트는 샤드 A의 docs컬렉션에서 발견될 수 있다는 말이다. 하지만 이들 도큐먼트가 어떻게 배치 되어있는지와는 전혀 관련이 없다.

분할과 마이그레이션

  • 청크의 분할과 마이그레이션은 샤딩 메커니즘의 핵심이다.
  • 샤드 클러스터를 처음 셋업할 때는 단지 하나의 청크만이 존재한다. 그렇다면 어떻게 해서 샤드 클러스터가 어려개의 청크를 가지게 될까?
  • 그것은 청크가 일정한 크기에 도달하면 분할 되기 때문이다. 디폴트 값은 64MB 혹은 100,000 도큐먼트(둘중 어느것이라도 먼저 해당되는 값)
  • MongoDB 샤드 클러스터는 청크를 샤드 사이에서 옮김으로써 균형을 맞춘다. 이것을 마이그레이션이라고 부르고 분할과는 달리 물리적인 작업이다

  • 마이그레이션은 밸런서라고 하는 소프트웨어 프로세스에 의해 관리된다.
  • 가장 많은 수의 청크를 갖는 샤드와 가장 적은 수의 청크를 갖는 샤드간 청크의 개수가 8개 이상 차이가 나면 균형을 일는 작업을 시작한다 이작이 일단 시작되면 청크는 청크 개수가 많은 샤드로부터 적은 샤드로 이동하게 되는데, 두 샤드가 대략 고르게 분포될 때까지 이 작업이 수행된다.

  • 이해가 안가도 걱정마라 다음 섹션에서 실험으로 이해시켜주리리…

9.2 샘플 샤딩 클러스터

9.2.1 셋업

  • 첫번째 단계 : 모든 mongod와 mongos프로세스를 시작하는 것이다
  • 두번쩨 단계 : 클러스터를 시작하는 일련의 명령을 실행하는 것이다.

샤딩 구성요소 시작

  • 2개의 복제셋에 대한 데이터 디렉토리를 생성
mkdir /data/rs-a-1
mkdir /data/rs-a-2
mkdir /data/rs-a-3
mkdir /data/rs-b-1
mkdir /data/rs-b-2
mkdir /data/rs-b-3

folk 옵션 : 프로세스를 데몬으로 실행하도록 한다

  • 이제 각 mongod 프로세스를 실행하자
sudo mongod --shardsvr --replSet shard-a --dbpath /data/rs-a-1 --port 30000 --logpath /data/rs-a-1.log --fork --nojournal
sudo mongod --shardsvr --replSet shard-a --dbpath /data/rs-a-2 --port 30001 --logpath /data/rs-a-2.log --fork --nojournal
sudo mongod --shardsvr --replSet shard-a --dbpath /data/rs-a-3 --port 30002 --logpath /data/rs-a-3.log --fork --nojournal
두번째 복제셋
sudo mongod --shardsvr --replSet shard-a --dbpath /data/rs-b-1 --port 30100 --logpath /data/rs-b-1.log --fork --nojournal
sudo mongod --shardsvr --replSet shard-a --dbpath /data/rs-b-2 --port 30200 --logpath /data/rs-b-2.log --fork --nojournal
sudo mongod --shardsvr --replSet shard-a --dbpath /data/rs-b-3 --port 30300 --logpath /data/rs-b-3.log --fork --nojournal
rs.initiate()
rs.add("arete:30100")
rs.add("arete:30101, {arbiterOnly:true}")

9.3.1 샤드 쿼리 타입

  • 샤드 클러스터에 질의 한다고 가정했을때 질의에 대한 결과를 리턴하기 위해 mongos는 얼마나 많은 샤드에 연결해야할까? 샤드 키가 쿼리 실렉터에 있는지 여부에 달렸다. 설정서버는 키의 범위를 샤드에 대해 매핑한 것을 가지고있다.