2011. 9. 28. 18:22

[mongodb] Replication

mongodb administrator의 가장 중요한 작업중 하나다.
replication(복사)을 구성하고 동작시키는 것이다.

replica(복제노드)는 장애 극복, data integrity(입력된 데이터가 변경/파괴되지 않은 상태),
에서 더 나아가,
확장성을 가지고 읽기(scaling-out read), data source의 offline batch 작업등등...
을 지원하는데 사용된다.

master - slave replication

master
|
|- slave
|
|- slave
|
|- slave
|
...


가장 일반적인 방식이다.
flexible하고 backup에 적합하다.
그리고 그외 장애극복 및 read scaling(규모의 read)등등이다.

기본적인 세팅은 master node와 하나 이상의 slave node를 시작하는 것이다.
각각은 master의 address를 알아야 한다.

master로 시작하고 싶으면 mongod --master로 한다.
slave로 시작하고 싶으면 mongod --slave -source master_address로 한다.
master_address는 master node의 주소로, 막 시작되었어야 한다.

이러한 세팅을 하나의 machine으로 구성하기도 쉽다.
우선, master가 저장할 directory를 구성하고 prot 10000으로 세팅한다.
$ mkdir -p ~/dbs/master
$ mongod --dbpath ~/dbs/master --port 10000 --master

이제 slave를 세팅할 차례인데, 다은 경로와 port를 만든다.

$ mkdir -p ~/dbs/slave
$ mongod -dbpath ~/dbs/slave --port 10001 --slave --source localhost:10000

모든 slave는 master node로 부터 복사되어야 한다.
현재로서는 slave로 부터 복사하는 machinism이 없는데,
slave는 그들 고유의 oplog가 없기 때문이다.

slave cluster의 숫자 제한은 없으나, 수천의 slave를 가지고 단일 master에 query하는
것은 처리 능력을 압도해 버리므로 좋지 않다.
현실적으로 12개 이하 정도의 slave 수는 동작을 잘 한다.

Options

--only
; slave node로 하여금 single database만 복사하도록 한다. (기본은 all)

--slavedelay
; master로 부터 operation 적용에 대해 delay한다(몇 초).
이는 사용자가 부주의로 delete/insert 하는 사고를 쉽게 대처해 준다.

--fastsync
; master node의 snapshot으로 부터 시작한다.
fullsync보다 빠르기 때문에, 시작시간을 빠르게한다.

--autoresync
; slave가 master로 sync를 피할때, funnsync를 자동으로 수행한다.

--oplogsize
; master의 oplog 크기를 설정한다.

Source 추가/삭제하기

localhost:27017에서 master가 동작중일떄,
mongod --slave --dbpath ~/dbs/slave --port 27018
을 통해 slave를 시작 할 수 있다.
물론 아래와 같이 shell에서도 지정할 수 있다.

만일 master가 localhost:10000이라면,
그러면,

use local
db.source.insert({"host":"localhost:27017"})

을 통해 slave를 위한 source를 추가하였다.

slave의 log를 보고싶다면, localhost:27017에 sync하여 가능하다.

만일 localhost:10000이 master이고 localhost:10001이 slave라면,

$ mongo localhost:10001
MongoDB shell version: 1.8.3
connecting to: localhost:10001/test
> use local
switched to db local
> db.sources.find()
{ "_id" : ObjectId("4e80007a0bdf33fb1db0739a"), "host" : "localhost:10000", "source" : "main", "syncedTo" : { "t" : 1317011581000, "i" : 1 }, "localLogTs" : { "t" : 0, "i" : 0 } }

와 같다.

* Replica sets (복사본 집합)

repilica set은 기본적으로 자동으로 장애를 극복하는 master-slave cluster를 의미한다.
master-slave cluster와 replica set의 가장 큰 차이점은 replica set은 단일 master를 가지지
않는다는 점이다. cluster에 의해 하나가 선출(elected)되고, 만약 현재의 master가 down되면
다른 node로 변경하게 된다. 여하튼, 그러나 master-slave cluster와 replica set은 그 차이점은
별로 없다. replica set은 항상 단일 master node(primary)와 하나 이상의 slave(secondary)를 가지고 있다.

만일, machine (1), machine (2)가 있다고 가정하자.

(1) primary -> (2) secondary와 같이 두개의 구성원으로 이뤄진 replica set이 있다.
만일, (1)이 down되면, (2)가 master가 된다.

(1) X (2) primary

그리고, (1)이 원상 복귀되면, 새 primary의 slave로 시작한다.
(1) secondary <- (2) primary

replica set의 장점은 모든것이 자동화되어 있다는 점이다.
set 그 자체는 수많은 관리자 작업을 수행하고 slave를 자동으로 승격한다. 그리고,
coinsistency(불일치, 동일 object를 나타내는 2개의 data가 있을때, 한개만 변경)에
빠지지 않음을 확신시켜 준다.
개발자가 사용하기에 쉽게 되어 있다. set에 몇개의 server를 지명하고, driver에서 자동으로
모든 server를 계산하고, 현재 master가 die되면 자동으로 극복하는 것을 처리한다.

초기 set

replica set을 세팅하기 위해서, master-slave cluster 구성에서 조금더 나아가야 한다.
일단, 2개의 server로 세팅하는 것으로 시작하자.
우선, data directory를 다음과 같이 설정한다.
$ mkdir -p ~/dbs/node1 ~/dbs/node2

시작하기전 결정해야 하는 사항이 있는데, 그것이 바로 replica set을 위한 이름이다.
우선 greenfishmongo라 하자.

server를 시작했다면, 새로운 옵션으로 --replSet이 필요하다.

$ mongod --dbpath ~/dbs/node1 --port 10001 --replSet greenfishmongo/localhost:10002
$ mongod --dbpath ~/dbs/node2 --port 10002 --replSet greenfishmongo/localhost:10001

만일 3번째 server를 구축한다면, 아래 두개중 한개로 전달할 수 있다.
$ mongod --dbpath ~/dbs/node3 --port 10003 --replSet greenfishmongo/localhost:10001
$ mongod --dbpath ~/dbs/node3 --port 10003 --replSet greenfishmongo/localhost:10001,localhost:10002

장점중 하나로 그들은 self-detecting하다. set에 한개를 추가한다면, mongodb는 나머지
node를 자동으로 connect한다.

이와같이 server로 구성했다면 server log에서 replica set이 초기화되지 않았다는 부분이
발견된다. 이는 shell에서 초기화를 해줘야 해결된다.
([startReplSets] replSet info you may need to run replSetInitiate -- rs.initiate() in the shell -- if that is not already done)

server중 하나를 shell에서 연결한다.

$ mongo localhost:10001/admin
MongoDB shell version: 1.8.3
connecting to: localhost:10001/admin
> db.runCommand({"replSetInitiate":{            
"_id":"greenfishmongo",
"members":[
{
    "_id":1,
    "host":"localhost:10001"
},
{
    "_id":2,
    "host":"localhost:10002"
}
]}})
{
 "info" : "Config now saved locally.  Should come online in about a minute.",
 "ok" : 1
}
와 같이 진행된다.

"_id":"greenfishmongo"
; set 이름

"members":[...]
set에 있는 server member를 설정한다. 이후 추가할 수 있다.
각 server document는 "_id"와 "host"를 가지는데,
전자는 unique id이고, 후자는 host를 의미한다.

local.system.replset namespace로 find하여 확인할 수 있다.

replica set의 node

어떤 시점에서 하나의 node는 primary고 그 이외는 secondary이다.
primary node는 필수 master이고, 이후 변경될 수 있다.

replica set에 공존하는 node의 type은 다음과 같다.

standard
; regular replica set node이다. 복사본의 full copy를 보관하고
새로운 primary를 선출하는데 참여하고 primary로 승격할 자격이 있다.

passive
; full copy를 보관하고 선출하는데 참여하지만 primary로 되지 못한다.

arbiter
; 단지 선출하는데만 참여한다. 어떤 data도 수신하지 못한다.
물론 primary로 되지 못한다.

standard와 passive 차이점은 scale을 더 조개느냐이다.
arbiter를 제외한 참여 node는 priority 설정이 있다.
0은 passive를 의미하며 primary로 선출되지 못한다.
non-zero는 높은 값으로 선출된다. 2개의 1과 1개의 0.5가 있다면,
3번째는 모든 1이 동작하지 못할때 승격될 수 있다.

standard와 passive는 다음과 같이 설정된다

members.push({
"_id":3,
"host":"localhost:10003",
"priority":40
});

members.push({
"_id":4,
"host":"localhost:10004",
"arbiterOnly":true
});

secondary node는 primary node의 oplog로 부터 pull하고 operation을 적용한다.
이는 master-slave와 유사하다. secondary node 역시 자신 고유의 local oplog에
operation을 적용한다. 그러나 primary가 되기 위한 능력을 위해서 이다.
oplog에 있는 operation은 증가하는 방향으로 단조 증가한다. 이 방향은
cluster의 임의의 node가 어떻게 최신의 data를 구성하는지 결정하는데 사용된다.

장애극복 및 primary 투표

만일 현재의 primary가 down된다면, 나머지 node은 새로운 primary node 선출을 위해 투표를
시도한다. 이러한 투표 작업은 primary에 도달할 수 없는 임의의 node에 의해 초기화된다.
새로운 primary는 set의 다수결로 선출된다. arbiter node는 참여하게 되고, 동점 상황일때
유용하게 사용된다. 새로운 primary는 가장 높은 우선순위를 가지게 된다.

primary node는 cluster에 있는 다른 node들이 몇개가 살아있는지 추적하기 위해 정보를
전송한다. 만일 대다수 실패한다면, primary는 자동으로 secondary 상태로 강등된다.
이는 primary가 network 분리등에 의해 격리됨에도 불구하고 계속 작업을 수행하는것을
방지한다.

primary가 변경될때마다 새로운 primary에 있는 data는 system의 가장 최근 data로 간주한다.
예를들어 이전의 primary node와 같이 다른 node에 의해 적용된 operaiton은 rollback된다.
물론 이전 primary node가 정상으로 돌아와도 마찬가지 이다.
이러한 rollback을 수행하기 위해 모든 node는 새로운 primary에 접돌할 때 resync 작업을
진행한다. 그들은 oplog를 통해 primary에 적용되지 않은 operation을 찾으며
그러한 operation에 의해 영향받은 document들의 가장 최신 copy를 구하기 위해 새로운 primary를
query한다. resync중인 node는 recovering이라고 읽으며, 해당 작업이 완료될 때 까지 primary
투표에 참여하지 않는다.

Slave에 operation 수행하기

이러한 master-slave의 가장 중요한 목적은 master node의 down이나 data loss때 장애를 극복하는
기능을 위해서이다. 또다른 slave의 용도가 있는데, backup을 수행하는데 사용된다.
또한 scaling out 상태의 read에도 사용된다.

규모로 읽기

규모로 읽기 위해서는 slave node를 통해 query하는 것이다. 그로인해 master의 부하는 줄어든다.
이러한 경우는 보통 다음의 autosharding으로 응용할 수 있다.

slave로 읽는 것은 쉬운편인데, master-slave 상태에서 query 처리를 위해 slave에 직접 연결하는
것이다. trick이 있다면, 특수한 query option이 있는데, 어떤 slave server를 쓸 것이냐?도 포함되어 있다.
(디폴트로 query는 slave에서 수행되지 않는다.)
이 옵션은 slaveOkay로 불리며, 모든 mongodb driver는 해당 mechanism을 제공하고 있다.
게다가 몇몇 driver는 자동으로 query를 slave에 분산하여 처리하는 기능도 제공하고 있다.

data processing을 위해 slave 사용하기

또다른 재미있는 테크닉은 slave를 극심한 작업이나 집계 작업으로 인한 master 성능 저하가
발생하는데 이를 떠넘기는 mechanism으로 사용하기도 한다.
이를 위해서 일반 slave로 시작하되 역시 --master 옵션을 추가하는 것이다.
즉, --slave와 --master를 같이 쓰는 것이다. 이는 slave에게도 쓰기 권한을 주는 것이며,
일반적인 mongodb의 master node로도 취급해 준다. 덧붙여 slave에 blocking 작업을 수행할 때,
master node에 어떠한 성능에 영향을 주지 않도록 한다.

참고)
만일 이러한 방법을 사용할 때에는,
master로 부터 복사된 slave에 어떠한 write도 해서는 안된다.
slave는 master를 제대로 mirror하기 위해 이러한 write에 대해 되돌리지 않는다.
slave는 처음으로 시작했을 때 복사된 database를 가져서는 안된다.
만일 그러했다면, database는 새로운 operation들에대해 update와 sync를 수행하지 않는다.

* 어떻게 동작하는가?

사된 mongodb는 최소한 2개의 server 혹은 node를 구성한다. 하나는 일반 client request를
처리하는 master이다. 다른 하나는 master에 저장된 data를 mirror하는 책임이 있는 slave이다.
master는 수행할 모든 operation의 record(기록)를 유지한다. slave는 주기적으로 이러한 새로운
operation을 master로 부터 polling한다. 그리고 복사된 data로 수행한다. master node의 모든
동일한 operation을 수행함으로서 slave는 master의 항상 최신 data로 유지할 수 있다.

oplog

master가 유지하는 operation record는 oplog(operation log)라 불린다.
oplog는 local이라는 특별한 database에 저장되며 collection은 oplog.$main이다.
oplog의 각 document는 master에서 수행할 단일 operation을 표현한 것이다.
몇개의 key와 다음을 포함하고 있다.

db.oplog.$main.find()
{ "ts" : { "t" : 1317191495000, "i" : 1 }, "op" : "n", "ns" : "", "o" : { } }
...
{ "ts" : { "t" : 1317191614000, "i" : 1 }, "op" : "i", "ns" : "a.b", "o" : { "_id" : ObjectId("4e82bfbef2fc774ee41c041d"), "c" : 1 } }
{ "ts" : { "t" : 1317191619000, "i" : 1 }, "op" : "n", "ns" : "", "o" : { } }
...

ts
; timestamp.

op
; 1byte의 operation type ("i"는 insert)

ns
; operation이 수행할 namespace

o
; 수행할 operation을 기술. insert인 경우 insert할 document가 들어감.

oplog의 중요한 부분은 database의 상태를 변경할 operation만 단지 저장하는 것이다.
예를 들어 query는 oplog에 저장되지 않는다. 즉, data를 쓰는 논리에만 oplog가
적용되도록 한다.

oplog에 저장된 operaton은 master 그 자신이 정확하게 수행했다고는 말할 수 없다.
이 operation은 저장전 변환되는데, 이는 slave에서 여러번 수행될 operation은
올바른 순서대로 적용되도록 하는 것이다.
(예를들어, "$inc"는 "$set"으로 변환된다)

마지막으로 이는 capped collection에 저장된다. oplog에 저장된 새로운 operation은,
자동으로 가장 오래된 operation 위치에 저장된다. 이는 oplog가 영원히 커지는것을
방지한다. 그 크기는 --oplogSize로 전달된다.

sync

slave가 처음 시작하면 master node로 부터 full sync를 하게된다.
slave는 master node로 부터 모든 document를 copy 하는데, 이는 명백히 비용이 드는 작업이다.
초기 sync가 완료되고 slave는 master의 oplog를 query하고 적용한다.

slave로 operation을 요청하는 application이 만일 master로 부터 수행해야할 operation이 아직도
많은 상태인 slave였다면 그 slave는 out of sync가 된다.

slave가 out-of-sync가 되면 복사는 중지되며 master로 부터 fully resync된다.
이러한 resync는 {"resync":1}에 의해 수행되며, --autoresync가 있다면 자동으로 수행된다.
어떻든, resync는 고비용 operation으로 피해야 한다.

이와 같이 slave의 sync를 방지하기 위해서, 큰 큰기의 oplog가 필요하다. 큰 oplog는
disk 크기를 많이 잡으나, 일반적으로 sync하는 비용과 trade-off이다.
(oplog 크기의 디폴트 값은 5% 이다)

상태 복사와 local database

local database는 내부 복사 상태 용도로 사용되는데, 이는 master와 slave 모두 적용된다.
local database의 이름은 local이며, 해당 내용은 절대로 복사되지 않는다.

slave의 list를 포함하는 master에 다른 복사 상태가 저장된다.
이는 다음과 같이 slave collection에 저장된다.

db.slaves.find()
{ "_id" : ObjectId("4e82bf6775761b5c62350281"), "host" : "127.0.0.1", "ns" : "local.oplog.$main", "syncedTo" : { "t" : 1317195164000, "i" : 1 } }

slave는 역시 local database에 상태가 저장된다. 이는 sources collection에 저장된다.

db.sources.find()
{ "_id" : ObjectId("4e82bf6775761b5c62350280"), "host" : "localhost:10000", "source" : "main", "syncedTo" : { "t" : 1317195224000, "i" : 1 }, "localLogTs" : { "t" : 0, "i" : 0 } }

복사를 위한 blocking

mongodb의 getLastError 명령시 w parameter를 추가하여, 개발자로 하여금 최신의 복사에 대한 정보를
구하는것을 보장한다
getLastError는 최소한 N server가 복사될때까지 block된다.

db.runCommand({getLastError:1, w:N})

만약 N이 없거나 2보다 작은 경우 즉시 리턴된다.
만약 N이 2라면 최소한 하나의 slave에서 복사가 이뤄질때까지 block된다.
master는 local.slaves에 저장된 "syncedTo" 정보를 이용하여 각 slave를 추적한다.

w가 지정되면 getLastError는 "wtimeout"을 추가로 받는데, 해당 작업의 timeout을
millisecond로 지정한 것이다. 디폴트로는 no timeout이다.

복사를 위한 blocking은 write 작업을 상당히 느리게한다.
2~3개의 중요한 operation에 적용하여 보다 효과적이고 안전하게 할 수 있다.

* Administration

진단

mongodb는 복사의 상태를 검증하는데 사용하는 몇가지 유용한 helper를 제공한다.
master에 연결될 때, 다음과 같은 정보를 볼 수 있다.

db.printReplicationInfo()
configured oplog size:   944.1375732421875MB
log length start to end: 5633secs (1.56hrs)
oplog first event time:  Tue Sep 27 2011 23:31:35 GMT-0700 (PDT)
oplog last event time:   Wed Sep 28 2011 01:05:28 GMT-0700 (PDT)
now:                     Wed Sep 28 2011 01:05:32 GMT-0700 (PDT)
> exit
bye
$ mongo localhost:10001
MongoDB shell version: 1.8.3
connecting to: localhost:10001/test
> db.printReplicationInfo()
{ "errmsg" : "neither master/slave nor replica set replication detected" }

역시 비슷하게 다음과 같이 slave의 정보를 구할 수 있다.
db.printSlaveReplicationInfo()
source:   localhost:10000
  syncedTo: Wed Sep 28 2011 01:09:18 GMT-0700 (PDT)
   = 17secs ago (0hrs)

Oplog 크기 변경하기

가장 간단한 방법은 master를 stop하고 local database file을 삭제하고 --oplogSize로 재시작하는 것이다.
이는 다음과 같다.
$ rm /data/db/local.*
$ mongod --master --oplogSize xxxx
(xxxx는 MB 크기)

이 작업 이후에는 모든 slave들도 재시작되어야 하며 --autoresync를 수행해야 한다.

미리 oplog 크기를 크게 잡아버리면 master node로 부터 downtime을 증가시키는
현상을 야기한다.

인증과 함께 복사

만일 mongodb 인증과 함께 복사하고자 한다면, configuration을 추가로 해야 한다.
master slave모두 user는 local database에 추개해야하는데, 동일한 user와 password이여야
한다. local database의 user는 admin의 user와 비슷한다.

slave가 master에 접속할 때, local.system.users에 저장된 user로 인증한다.
시도한 첫 username이 "repl"이고 해당 user가 없다면 local.system.users에 있는 처음것을
사용한다. 따라서 인증과 함께 복사하려면 master와 slave모두 다음과 같이 실행해야 한다.

use local
switched to db local
db.addUser("repl", password);
{
   "user":"repl",
   "readOnly":false,
   "pwd":"..."
}
그럼 slave는 master로 부터 복사를 수행할 수 있다.

'Research > mongodb' 카테고리의 다른 글

[mongodb] Sharding  (0) 2011.10.06
[mongodb] Administration  (0) 2011.09.23
[mongodb] Advanced topics  (0) 2011.09.23
[mongodb] Aggregation (MapReduce)  (0) 2011.09.22
[mongodb] Indexing  (0) 2011.09.22