[발 번역] MongoDB Write Lock

해당 내용은 http://blog.pythonisito.com/2011/12/mongodbs-write-lock.html?m=1 의 글을 발번역 한 것입니다. 오역에 주의하세요. 실제로 1.8 과 2.0 에 대한 소스의 변화는 다음번에 확인해서 어떤 변화가 있는지 한번 추가로 포스팅 하도록 하겠습니다.

 

MongoDB Write Lock

아는 사람들은 알고, 모르는 사람은 모르겠지만, MongoDB는 Process-Wide write Lock을 가집니다.( 역자 주: 프로세스가 하나의 Write Lock을 가지고 있다는 뜻입니다.  )  이런 Primitive Locking Model 때문에 순수 데이터베이스주의자들에게서 조롱거리가 되기도 합니다. 현재 MongoDB의 로드맵에는 콜렉센 레벨 락이라든지, 데이터베이스 레벨 락이 들어가 있긴하지만, 아직은 구현되지 않았습니다.  MongoDB Version 2.0 에서는 locking-with-yield  라는 기법이 들어갔다고 발표했습니다.  Write Lock 이 미치는 영향과 lock-with-yield 의 성능 향상이 얼마나 되는지 궁금해졌고, MongoDB 1.8 과 MongoDB 2.0의 간단한 벤치마크를 하기로 결정했습니다.

PAGE FAULTS AND WORKING SETS

MongoDB 는 디스크에 대한 쓰기를 위해 운영체제의 Virtual Memory를 사용합니다.( 역자 주: 정확히는 Memory Mapped 방식을 이용합니다.) 이 방식은 데이터 베이스의 모든 내용을 OS의 Virtual Memory에 매핑하고, OS 스스로 해당 페이지가 ‘hot’ 한지, 물리 메모리에 있을 필요가 있는지와, ‘cold’ 한지, disk로 swap-out 될 수 있는지 결정하게 합니다.( 역자 주:  현재의 대다수의 OS는 Virtual Memory를 이용하고, 이와 함께 페이징을 이용합니다. 페이징을 하게 될때, 실제 메모리 크기 때문에 물리 메모리에 올라가 있는 것과,  디스크에 있는 것이 생기게 됩니다. 이 때 메모리로 실제 내용을 올리는 것이 ‘Swap-In’, 디스크로 내리는 것을 ‘Swap-Out’ 이라고 합니다. 이 뿐만 아니라, 실제 메모리에 없는 페이지에 접근하게 되면, Page Fault 가 발생하고,  Page Fault 가 발생했을 때, 해당 Fault Handler 에서  필요한 메모리를 찾아서 실제 물리 메모리와 매핑시키게 됩니다. – 혹시나, 여기서 한번 더 Page Fault 가 발생하면, Second Fault 라고 해서 OS가 오류를 내게됩니다. – 또는 죽습니다.  – 자세한 건 리눅스 커널의 이해등의 OS 책을 보시길 추천드립니다.)  MongoDB 역시 메모리에 있는 데이터베이스가 디스크와 주기적으로 싱크합니다.(MongoDB는 데이터를 유실을 방지하는 저널링 시스템도 가지고 있지만, 이 부분은 해당 글의 범위를 넘어갑니다.)

이것이 무슨 말이냐 하면, 특정 메모리 페이지가  메모리에 실제로 존재하는가 아닌가에 따라서, 서로 다른 동작을 하게 된다는 것입니다. 메모리에 없다면, 이걸 Page Fault 라고 부릅니다.  query나 update는 디스크에서 필요한 데이터를 로딩하는 매우 느린 동작을 기다려야 합니다. 반대로 데이터가 메모리에 있다면(OS는 이걸 ‘hot’  페이지라고 합니다.) , update는  단순히 System RAM에 쓰기만 합니다. 매우 빠릅니다. 이것이 10gen( MongoDB 제작사 ) 에서 가능한 전체 Working Set(빈번하게 접근하게 되는 데이터)을 유지할 수 있는 충분한 메모리를 장착하라고 항상 추천하는 이유입니다. (역자 주: 여기서 메모리의 의미는 실제 물리 메모리를 의미합니다. Page Fault는 가상 메모리가 실제 메모리 공간보다 크므로, 이를 실제 메모리에 매핑하지 못해서 발생하는 현상입니다. – 실제로는 물리 메모리보다 큰 메모리 공간을 사용하기 위한 기법으로 고안이 되었습니다.)

만약 이렇게 할 수 있다면 Global write Lock은 거의 영향을 미치지 못합니다.  쓰기 완료를 위해서 읽기가 대기하는 시간은 겨우 수 Nanoseconds 라서 문제가 되지 않습니다.( 이걸 실제로 측정하지는 못했지만, Global Write Lock의 획득 시간이 실제 Write 작업보다 시간이 많이 더 걸릴까요? )  결국, non-faulting 상황에서 write 수행율이 얼마나되는지 측정했고, 거의 영향이 없다라는 것을 발견했습니다. ( 역자 주: 원문에 But this is all kind of hand-waving at this point. 라는 문장이 있는데, 무슨 뜻인지 모르겠네요. 아시는 분 알려주세요. )

2.0  TO THE RESCUE?

Write를 하는 중에, Lock을 획득하고,  Lock을 잡고 있는 중에, 쓰기를 시도하고, Page Fault 가 발생한다면, Page Fault 는 Non Fault 동작보다 40,000 배 정도 더 느리기 때문에, 문제를 일으킬 수 있다. MongoDB 2.0이나 그 상위 버전에서는 Page Fault 의 가능성을 감지해서 Page Fault 전에 Lock을 해제 함으로써 이 문제를 해결했다. (역자 주: 그래서 write lock with yield 인가 봅니다. 직역하면 Write Lock 과 포기 정도일까요?)  이것이 정말로 측정하길 원했던 것입니다. 이전의 ‘Write Lock’ 에 비해서  ‘write lcok with yield’ 의 효과는 무엇일까?

BENCHMARK SETUP

첫번째로, 가능한 재현 가능한 벤치마크를 만들고 싶고, 내 노트북에 돌리는 것들로 인해서 발생할 요소들에 대해서 걱정하고 싶지 않아서, 아마존 EC2에서 벤치마크를 돌리기로 결정했다. ( 작성한 코드들은 여기(here  – fill_data.py, lib.py combined_benchmark.py) 에 있습니다. 만약 단순히 결과를 보고 싶다면 ami-63ca1e0a off AWS 이미지를 이용하기 바랍니다. ) 임시 디스크를 database store로 사용하는 ubuntu 서버를 m1.large 장비에서 사용하였습니다. 또한 noatime 성능 향상을 목적으로 임시 디스크를 ext3에서 ext4로 재포맷했습니다. MondoDB 는 10gen .deb repositories 로 부터 version 1.8.4(mongodb18-10gen) 와 2.0.2(mongodb-10gen)를 설치했습니다.

데이터베이스는 하나의 Collection을  1억5천만개의 112-byte 다큐먼트로 채웠습니다. 최종 데이터 크기는 m1.large 장비의 메모리가 7.5GB 이므로 이것을 초과하기 위해서 선택했습니다.(역자 주:  대략 150MB * 112 하면 16.8GB 정도 되겠네요.) (Collection의 크기는 인덱스를 포함해서 가상메모리에서 21.67GB 정도 차지합니다.)  저널링은 1.8과 2.0 모두 off 시켰습니다.

Read 와 동시에 발생하는 Write의 성능을 측정하고 싶었고, 이를 위한 로드를 발생하기 위해서, Python 벤치마크 코드는 gevent를 이용해서 두 개의 greelets 를 생성했습니다. 하나는 10,000개의 다큐먼트 Working Set 에서 랜덤하게 읽는 것이고, non-faulting 상태에서의 read 성능을 보기 위한 것입니다. 두 번째 greenlet은 1억 5천만개의 전체 문서 중에서 특정 주기로 랜덤하게 Write 를 하는 것입니다.  write 와 read 측정 결과를 그래프로 생성했습니다.

RESULTS

첫번째로 non-faulting write 와 non-faulting read 의 성능을 보여주길 원했고, read 와 마찬가지로 처음 10,000 개의 Working Set 안에서만 write가 되도록 제한했습니다. 그 결과, write lock 은 거의 성능 저하가 없었습니다. 그리고 이 경우에 1.8과 2.0은 거의 차이가 없었습니다.

다음 테스트에서는 1.8과 2.0의 차이를 뚜렷하게 보여줍니다. 1.8에서는 faulting write 가 나타나면 read 성능이 바로 떨어지는 것을 볼 수 있습니다. 초당 48 write를 하는 동안에 거의 읽기가 발생하지 않는 습니다. 2.0 에서는 같은 Write 레벨에서 최대 성능의 60% 정도를 내고 있습니다.( 초당 48 fauling write 이상 부터는  page fault를 처리하는 것이 디스크의 전체 성능의 대부분을 차지해서 실제 성능을 측정하기 어렵게 만듭니다.)

 

CONCLUSION

MongoDB 의 Global Write Lock이 사라져도 전혀 슬프지 않다. 하지만, 여기서 본 것처럼,  non-faulting reads 와  writes 상황에서는 기대했던 것 보다 성능 차이가 거의 없다.( Working Set의 크기가  RAM의 사이즈에 맞춰져 있다면), write lock이 성능을 떨어트리는 것은 write 수 가 증가하거나, faulting writes 가 발생할 때이다.  write with yield 라는 2.0의  접근방법은 가끔씩 발생하는 fauting writes의 성능에 대한 나쁜 영향을 완화시킨다.