프로세스별/서버별로 다운된 상황에서 실제로 어떻게 되는지 테스트 해봄.




클러스터는 5대의 서버에 각 프로세스들이 아래와 같은 형태로 구성되어 있다고 가정함.






우선 프로세스별로 죽여보면서 테스트.


config

클러스터의 메타데이터를 저장하는 config 프로세스의 경우 3개의 프로세스가 모두 죽어도 read/write는 정상 동작함.

하지만 내부적으로는 write가 아무리 일어나도 chunk split이 되지 않는다는 문제점이 있음.

(config 프로세스가 하나만 죽어도 chunk split은 일어나지 않음)

죽었던 config 프로세스가 살아나면 다시 chunk split이 일어나기 시작하지만, 

config 프로세스가 죽어 있었던 동안 split이 일어나지 않아 정상적인 chunk size보다 커진 chunk가 index 정책에 따라 insert/update가 일어나지 않을 경우 자동 split이 되지 않으므로 수동으로 split을 해줘야 함.


결론: config 프로세스가 죽어도 read/write 정상동작은 보장되므로 쫄지 말고 침착하게 config 프로세스를 살린 후 chunk 들을 점검하여 필요하다고 판단되면 수동으로 split 해주는 정도면 될 듯.




mongos

어플리케이션에서 MongoDB 클러스터로 접근하는 프로세스인 mongos의 경우,

접속, 읽기/쓰기로 나눠서 테스트 함.


접속

connection string에 1개의 mongos만 지정하여 죽어있는 mongos로 접속시도를 하면 당연히 예외가 발생.


connection string에 여러개의 mongos를 지정하고 접속할 경우,

1개 이상의 mongos가 살아있다면 정상접속모든 mongos가 죽었다면 예외 발생.



읽기/쓰기

무한루프로 read/write를 반복하는 프로세스를 띄워 놓고 테스트.


1개의 mongos에 접속하고 있는 상태에서 read/write 하던 중, 해당 mongos가 죽으면 당연히 예외가 발생함.


2개 이상의 mongos에 접속하고 있는 상태라면,

1개 이상의 mongos만 살아있다면 정상동작, 모두 죽으면 역시 예외가 발생함.


결론: mongos 프로세스가 죽을 상황에 대비해 접속시 connection string에 여러개의 mongos를 지정해서 접속하는 것이 좋을 듯.




arbiter

arbiter는 primary를 선출하는데만 관여하므로, arbiter가 죽어도 read/write는 정상동작함.

최악의 경우, arbiter가 죽은 상태에서 primary까지 죽으면 secondary가 primary로 선출되지 못하므로 write 시에 예외가 발생하면서 실패함.


결론: 하드웨어 장애로 서버가 다운되더라도 가용성을 보장하려면 최소한 ReplicaSet의 arbiter와 primary는 분리되어 있어야 함.




primary

무한루프로 read/write를 반복하는 프로세스를 띄워 놓고 테스트.


write

  - primary가 죽은 직후 예외가 발생하며 실패하다가 secondary가 primary로 선출되고 나면 정상동작.


read

  - read preference에 따라 다른 결과가 나옴.


 ReadPreference 

 결과 

 RP_PRIMARY

 예외가 발생하다가 secondary가 primary로 선출되고 나면 정상동작

 RP_PRIMARY_PREFERRED

 secondary만 살아있다면 정상동작 

 RP_SECONDARY

 primary가 죽은 시점까지는 정상동작하지만, secondary가 primary로 선출되고 나면 해당 ReplicaSet에 secondary가 사라지게 되므로 예외 발생 

 RP_SECONDARY_PREFERRED

 정상동작 

 RP_NEAREST

 정상동작 





secondary

무한루프로 read/write를 반복하는 프로세스를 띄워 놓고 테스트.


write

  - 정상동작


read

  - read preference에 따라 다른 결과가 나옴.


 ReadPreference

 결과 

 RP_PRIMARY

 정상동작 

 RP_PRIMARY_PREFERRED

 정상동작 

 RP_SECONDARY

 예외발생 

 RP_SECONDARY_PREFERRED

 정상동작 

 RP_NEAREST

 정상동작 



primary 혹은 secondary가 죽었을 때 모두 정상동작 하는 ReadPreference는 아래 2가지 이므로 상황에 맞게 적절한 걸 선택해서 사용하면 될 듯.

RP_SECONDARY_PREFERRED

RP_NEAREST








다음은 서버별로 다운되는 상황을 가정한 테스트.


mongos1, mongos2에 접속한 상태에서 무한루프로 read/write를 반복하며 테스트.


mongodb-001


 프로세스 

 결과 

 mongos1

 다른 mongos들이 살아있으면 문제 없음.

 config1

 read/write에는 문제가 없으나 복구 이후 chunk 상태 점검 필요 

 shard1_arbiter 

 복구 전까지 shard1_primary만 안 죽으면 문제 없음 

 shard2_arbiter

 복구 전까지 shard2_primary만 안 죽으면 문제 없음 



mongodb-002


 프로세스

 결과 

 mongos2

 다른 mongos들이 살아있으면 문제 없음. 

 config2

 read/write에는 문제가 없으나 복구 이후 chunk 상태 점검 필요 

 shard1_primary secondary가 primary로 선출되기 전까지 write 예외 발생.
write가 성공할 때까지 재시도하는 로직으로 구성되어 있을 경우 문제 없음. 


mongodb-003


 프로세스 

 결과 

 mongos3 

 다른 mongos들이 살아있으면 문제 없음. 

 shard1_secondary  

 read시 ReadPreference가 RP_SECONDARY_PREFERRED 혹은 RP_NEAREST 로 되어 있으면 문제 없음. 


mongodb-004


 프로세스

 결과 

 mongos4

 다른 mongos들이 살아있으면 문제 없음. 

 config3 

 read/write에는 문제가 없으나 복구 이후 chunk 상태 점검 필요

 shard2_primary 

 secondary가 primary로 선출되기 전까지 write 예외 발생.

write가 성공할 때까지 재시도하는 로직으로 구성되어 있을 경우 문제 없음. 


mongodb-005


 프로세스 

 결과 

 mongos5 

 다른 mongos들이 살아있으면 문제 없음. 

 shard2_secondary 

 read시 ReadPreference가 RP_SECONDARY_PREFERRED 혹은 RP_NEAREST 로 되어 있으면 문제 없음. 



서버 단위 다운 상황 테스트의 결론은 다음과 같음.

- 서버 단위로 하나씩 장애가 발생해도 write시에 예외처리만 잘 되어 있고, read시에 ReadPreference만 RP_SECONDARY_PREFERRED 혹은 RP_NEAREST 중 적절한 상태로 되어 있으면 전체 클러스터의 가용성은 보장됨.

- 단, config 프로세스가 포함된 서버가 다운되었을 경우, 복구 이후에 chunk 상태를 점검할 필요는 있음. (하지만 안해도 가용성에는 문제가 없음...;)







각 테스트를 종합하여 가용성에 대한 결론은 다음과 같음.


- 접속/쓰기/읽기 시 아래 소스코드처럼 처리가 되어 있다면 프로세스나 서버 단위로 하나씩 다운되는 정도의 상황에서는 MongoDB 클러스터의 가용성 보장.


// 접속
// connection string에 mongos를 여러개 지정.
try {
    $m = new MongoClient('mongodb://USER:PASS@mongodb-001.XXXX.com:27017,mongodb-002.XXXX.com:27017,mongodb-003.XXXX.com:27017');
} catch (Exception $e) {
    // mongos 3개 전부 다운된 상태. 예사 문제가 아님...
}
  
  
  
// 쓰기
// 예외처리를 통해 primary가 죽어서 write가 실패했을 경우,
// secondary가 primary로 선출되어 write가 정상동작할 때까지 재차 시도하는 형태가 좋을 듯.
$start_time = time();
$limit = 10; // 10초간 반복
$write_succeeded = false;
while (time() - $start_time < $limit) {
    try {
        $r = $m->DB->COL->insert($data);
        // $r 로 검증을 빡세게 할 수도 있음.
    } catch (Exception $e) {
        // 예외 발생. 재시도.
        sleep(1);
        continue;
    }
  
    // 예외가 없었을 경우 쓰기성공
    $write_succeeded = true;
    break;
}
// 10초 동안 쓰기 실패
if ($write_succeeded === false) {
    // 예사 문제가 아님...
}
  
  
  
  
// 읽기
// ReadPreference만 RP_SECONDARY_PREFERRED 혹은 RP_NEAREST 로 해놓으면 가용성 보장
$m->setReadPreference(MongoClient::RP_SECONDARY_PREFERRED);
$q = array('date' => 20150115);
$r = $m->DB->COL->find($q);
 
 
 





덧붙여, 논리적으로 봤을 때 동일한 ReplicaSet의 arbiter와 primary가 같은 서버상에 있지만 않으면 가용성이 보장된다고 간주할 수 있으므로,

서버 수에 따라 아래와 같은 형태로 구성하면 가용성을 보장할 수 있을 것 같음.


서버 4대로 구성할 경우 아래처럼.

arbiter를 각 secondary와 함께 배치.




서버 3대로 구성할 경우에는 2가지 경우를 생각해 볼 수 있는데,

write가 read보다 많은 상황이라면 아래 예제처럼 primary를 분리하고,

read가 write보다 많은 상황이라면 반대로 secondary를 분리하면 될 것 같음.





마지막으로 클러스터 구성 미니멈 사이즈인 서버 2대의 경우,

가용성을 생각한다면 아래처럼 각 ReplicaSet의 primary와 secondary/arbiter를 묶어서 분리해주면 될 듯.

config3은 그냥 아무데나 때려 박고...;













Posted by bloodguy
,