Database/MongoDB

MongoDB 영속성 (Persistence)

BabyTT 2019. 6. 6. 18:44

1. Journaling 

 쓰기를 수행할 때 몽고디비는 쓰기에 대한 정확한 디스크 위치와 변경된 바이트를 포함한 저널을 생성한다. 서버가 갑자기 멈춘다면 시작 시 저널은 종료되기 전에 디스크에 플러시 되지 못한 모든 쓰기를 재현하는 데 사용된다.

 데이터 파일은 기본적으로 60초마다 디스크에 플러시되기 때문에 저널은 단지 60초 정도의 쓰기 데이터를 가지고 있으면 된다. 저널링은 이러한 목적으로 몇 개의 빈 파일을 미리 할당한다.

 몽고디비를 오래 실행한 후 저널 디렉터리를 살펴보면 _j.6217, _j.6218, _j.6219와 같은 파일을 확인할 수 있는데 이것이 현재 사용되고 있는 저널 파일이다. 숫자는 몽고디비가 실행되고 있으면 계속해서 증가한다. 정상 종료 시 저널 파일은 필요 없기 때문에 제거 된다.

 시스템이 갑자기 고장나거나 kill -9 명령으로 종료되면, 시작 시 mongod는 많은 양의 체크섬 행을 출력하면서 저널 파일을 재현한다.

 

- 커밋 배치작업 계획 하기

 데이터의 몇 메가바이트가 기록되면 기본적으로몽고 디비는 100ms마다 저널에 기록한다. 이는 몽고디비가 변경 사항을 배치로 커밋함을 의미한다. 모든 쓰기는 즉시 디스크에 플러시되지 않지만 기본 설정으로는 시스템이 갑자기 고장 나도 100ms 이상의 쓰기를 유실할 가능성은 없다.

 하지만 이러한 보장은 일부 어플리케이션에는 충분히 강력하지 않기 때문에 더 강력한 영속성을 보장할 수 있는 몇 가지 방법이 있다. getLastError에 j 옵션을 넘겨주어 쓰기가 영구적으로 기록되도록 보장할 수 있다. getLastError는 이전의 쓰기가 저널에 기록되기를 기다리고 저널링은 쓰기의 다음 배치저널을 위해 (100ms) 대신 30ms을 기다린다.

> db.foo.insert({"x" : 1})
> db.runCommand({"getLastError" : 1, "j" : true})
> // {"x" : 1} 문서는 이제 안전하게 디스크에 기록된다.

 모든 쓰기에 대한 옵션으로 "j" : true 를 사용하면 쓰기 속도는 기본적으로 초당 33번 쓰도록 조절된다.

 (1회 쓰기 / 30ms) * (1000ms/초) = 33.3 회 쓰기 / 초

 

 일반적으로 쓰기를 디스크에 플러시하는 데 오래 걸리지 않기 때문에 몽고디비가 매 쓰기마다 커밋하는 대신에 대다수의 쓰기를 배치로 처리하면 쓰기 속도가 향상된다. 하지만 이 옵션은 거의 중요한 쓰기일 때만 사용한다..

 하나의 쓰기를 커밋해도 이전의 모든 쓰기가 커밋된다. 그러므로 50개의 중요한 쓰기가 있다면 (j 옵션 없이) "일반적인" getLastError를 사용하고 마지막 쓰기 후 j옵션을 주고 getLastError를 호출한다. 성공한다면 50 개 쓰기가 모두 디스크에 안전하게 플러시될 것이다.

 들어오는 쓰기 연결이 많다면 쓰기를 병렬로 발생시켜 j 옵션 사용으로 이한 속도 저하를 완화할 수 있다. 이는 응답 대기 시간이 오래 걸리더라도 처리량은 늘릴 수 있다.

 

- 커밋 주기 설정하기

 저널링이 덜 발생하도록 만드는 또 다른 옵션은 저널 커밋 시간 간격을 짧게 (혹은 길게) 하는 것이다. journalCommitInterval 값을 2ms에서 500ms 사이로 설정하려면 setParameter 명령을 실행한다. 이는 매 10ms 마다 저널을 커밋한다

> db.adminCommand({"setParameter" : 1, "journalCommitinterval" : 10})

 명령행에서 --journalCommitInterval 옵션을 주어 설정할 수도 있다.

 시간 간격 설정과 관계없이 "j" : true로 getLastError를 호출하면 시간 간격 설정이 1/3로 줄어든다.

 클라이언트가 저널을 플러시하는것보다 더 빨리 쓰기를 시도하면 mongod는 저널이 디스크에 쓰는 것을 끝낼 때까지 쓰기를 중한다. 이는 mongod가 쓰기를 제한하는 유일한 시간이다.

 

2. 저널링 끄기

 저널링은 j 옵션을 설정하지 않더라도 몽고디비의 쓰기 속도에 영향을 준다. 데이터의 성능 저하를 감수할 정도의 가치가 없다면 저널링을 꺼 놓는다. 저널링 비활성화의 단점은 몽고디비의 데이터 무결성 보장 방법이 없다는 것이다. 시스템 장애 상황에도 계속해서 데이터베이스가 작동하도록 해야 한다면 몇 가지 옵션이 있다.

 

- 데이터 파일 교체하기

 복제 셋이라면 멤버를 정지시키고 데이터 디렉터리에 있는 모든 것을 삭제한 후 백업에서 다시 시작하여 재 동기화 할 수 있다.

 

- 데이터 파일 복구하기

 mongod에는 복구 툴이 있는데 mongod 자체 내장된 repair와 mongodump 에 내장된 좀 더 강력한 repair다. mongodump 복구는 더 많은 데이터를 찾아내지만 시간이 오래 걸린다. 게다가 mongodump의 복구를 사용하면 재시작하기 전에 여전히 데이터 복원이 필요하다. 

$mongod -dbpath /path/to/corrupt/data --repair

 몽고디비는 복구를 실행할 때 27017 포트를 리스닝하지 않지만 진행 중인 작업의 로그는 볼 수 있다. repair는 존재하는 데이터의 온전한 복사본을 만들기 때문에 80GB의 데이터를 가지고 있다면 80GB의 여유 공간이 필요하다. repair는 --repairpath 옵션을 지원한다. 이는 '비상용 드라이브'를 마운트하도록 허용하며 주 디스크에 충분한 공간이 남아 있지 않다면 마운트된 드라이브에서 데이터를 복구한다.

$mongod -dbpath /path/to/corrupt/data --repair --repairpath /media/external-hd/data/db

 repair 가 종료되거나 오류를 출력하더라도 걱정할 필요는 없다. 복구는 새로운 파일에 모든 출력을 기록하며 마지막까지는 원본 데이터 파일은 변경하지 않기 때문에 원본 데이터 파일이 복구를 시작할 때보다 악화되는 않는다.

$mongodump --repair

- mongod.lock 파일

 데이터 디렉터리 안에는 저널링 없이 실행할 때 중요한 mongod.lock 이라고 불리는 특별한 파일이 있다. (저널링이 실행 중이라면 절대 보여서는 안된다.)

 mongod는 종료할 때 mongod.lock을 지우기 때문에 이 파일이 없다면 mongod는 시작 시 정상적으로 종료되었음을 안다. 반대로 이 파일이 지워지지 않았다면 mongod는 정상적으로 종료되지 않았음을 안다. 

 mongod가 전에 비정상적으로 종료된 것을 감지하면 재시작을 허용하지 않기 때문에 사용자는 데이터의 온전한 사본을 가져와야 한다. mongod.lock은 지우지 않아야 한다.

 lock파일을 지우지 않는 중요한 이유 중 하나는 강제 종료 조차 알아차릴 수 없기 때문이다. 서버가 꺼지기 전에 mongod가 꺼지도록 관리해야 하지만 그렇지 않을 수도 있다.

 

3. 몽고디비가 보장하지 않는 것

 하드웨어 문제나 파일시스템 버그는 보장되지 않는다. 예를 들어 저렴하고 오래된 하드 디스크는 쓰기가 실제로 기록된 때가 아니라 쓰기 작업 큐에 들어가 있는 동안 쓰기가 성공했다고 보고 한다. 몽고디비는 이 수준에서 틀린 보고에 대응할 수 없기 때문에 시스템이 갑자기 고장 나면 데이터가 유실 될 수 있다.

 

4. 데이터 손상 확인

> db.foo.validate()
{
   "ns" : "test.foo",
   ....
   },
   "valid" : true,
   "errors" : [],
   "warning" : "Some checks omitted for speed. use {full:true} option to do more thorough scan.",
   "ok" : 1
}

 유효성 검사에서 출력된 내용 대부분은 collection 내부 구조에 대한 설명인데 이는 디버깅하는데 크게 유용하지 않다.

 인덱스가 아닌 콜렉션에서만 유효성 검사를 실행할 수 있기 때문에 일반적으로 인덱스를 꼼꼼히 살펴보지 않는다면 손상 여부를 확인할 수 없다. 적합한 인덱스를 힌트로 주고 콜렉션의 모든 문서에 대해 쿼리를 실행하여 인덱스를 꼼꼼히 살펴봐야 한다.

 유효하지 않는 BSONObj에 대한 선언이 있다면 이는 대체로 손상이다. 최악의 오류는 pdfile이 언급되는 것인데 기본적으로 몽고디비 데이터 스토지의 핵심이기 때문에 pdfile에서 발생한 선언은 데이터 파일이 손상되었음을 보증한다.

[initandlisten] Assertion: 10334:
   Invalid BSONObj size: 285213831 (0x87040011)
   first element: _id: ObjectId('4e5efa454b4ae20fa6000013')

 보이는 첫 번째 요소가 쓸데없는 쓰레기 값이라면 다음처럼 실행해서 손상된 문서를 제거할 수 있다.

> db.remoe({_id: ObjectId('4e5efa454b4ae20fa6000013')})

 

5. 복제와 영속성

 복제 셋 멤버의 과반수 이상 쓰기와 관련된 문제점으로 인해 복제 셋에 쓰기는 셋의 과반수에 기록될 때까지는 롤백될 수도 있다. 이러한 옵션과 저널링 옵션을 함께 설정한다면 다음처럼 할 수 있다.

> db.adminCommand({"getLastError" : 1, "j" : true, "w" : "majority"})

 쓰기는 primary에 영구적으로 써지고 Secondary에 써지도록 보장한다. 이론적으로는 쓰기가 써지기 시작해서 저널에 써질 때까지의 100ms 사이에 과반수가 넘는 서버가 깨질 수 있는데 이런 경우 해당 쓰는 현재 프라이머에서 롤백된다.