Backend 언어/Python Django

GC, 그대로 두면 아니되는 이유

민초부 2020. 2. 17. 22:38
반응형

   - 전 포스팅, GC에 대한 나의 의식 흐름은 아래와 같이 바뀜

      ~ 글을 쓰기 전 : 좋은거 아닌가 --> 글을 쓰면서 : 마냥 좋은건 아니구나 --> 글을 마무리하면서 : 잘 써야하는거구나

      그래서 이 의식을 놓치기 전에 인스타그램을 예시로 후딱 정리하게씀 (인스타그램이 python garbage collection을 없앤 이유)

 

   - GC를 없애기 전의 인스타그램 상황 

      ~ 웹서버는 django의 multi-process mode로 동작하고 WAS는 pre_fork모드를 이용하여 uWSGI 서버를 이용한다. 

      ~ 인스타그램은 내부적으로 master process spawning, 프로세스가 생성되고 나서 공유 메모리는 2/3에 불과했음

 

   - 공유 메모리? : Linux kernel에서는 메모리 관리를 위하여 copy on write 방법을 사용하여 메모리 관리를 하고 있음. 쓰기 작업이 이루어지는 순간에 페이지 전체를 copy하게 되고 다른 read작업이 이루어지는 process들은 copy원본의 메모리를 공유하며 사용하여 메모리를 아끼고 있음. 

   - 왜 이런 문제가 발생하였는가? : 파이썬의 Reference counting방식의 gc때문이였다. GC를 수행하기 위해 파이썬은 object들을 읽으면서 참조를 파악하고 reference count를 update하는데 이때 copy on write가 이루어지고 결과론적으로 python이 object들을 읽을 때 copy를 하는 copy on read가 발생하게 되는것이다. 

 

   - 처음에 인스타그램은 python의 code objects를 의심하였다. 위의 문제가 값이 변하지 않는 code objects에서조차 이루어지냐는 논점이였고 이에 python의 code objects인 pyCodeObejct의 reference counting을 없애주었지만 해당 문제는 여전히 발생하였다. 

   - 이에 그 다음으로 발견한 것이 gcmodule.c에 있는 collect였고 이 함수는 GC가 발생하는 순간에 바로 호출이 된다. 기본적으로 설정되어 있는 threshold의 값은 매우 낮기 때문에 GC를 생각보다 자주 호출하게 되고 이 과정에서 0~2세대의 threshold값이 저장되어 있는 list를 계속 shuffle하고 update하기 때문에 위의 문제인 CoW가 발생하였던 것이다. 

 

   - Ok GC를 꺼보자.  : 인스타그램은 이에 바로 gc.disable()로 껐는데도 계속 이런 현상이 발생하였다. 왜 그런가 보았는데 여전히 GC는 이루어졌고 third party library에서 gc.enable()을 호출하고 있었다. 이에 gc.set_threshold(0)으로 세팅해주자 GC는 일어나지않았고 공유메모리도 90%까지 증가하였음 더불어 GC threshold연산하는 메모리를 아껴 Django 성능까지 높힐 수 있었다. 

   - 하지만 개발 환경에서 web server의 restarting속도가 현저히 느려지는 현상이 발생하였음 이는 결국 또 Python Interpreter종료 시점 에 마지막으로 한번 더 GC를 실행하는데 이 때 모든 메모리를 잡아먹고 디스크 사용률이 100%까지 올라갔음 이를 해결하기 위해서 uWSGI의 python plugin Py_Finalize를 주석처리 하여 이 현상도 없앨 수 있었다. 

 

 즉 Python GC를 주석처리하여 인스타그램은 8GB에 가까운 메모리를 아낄 수 있었고, CPU의 IPC(Instructions per cycle)을 10%가량 증가시킬 수 있었다.

(근데 이렇게 GC를 끄고 사용하면 Python 내부적으로 막고 있던 순환참조나 이런 것들이 발생하는 리스크가 있긴한데 이건 개발하면서 유의하여 사용하는 방법뿐일꺼라 생각한다. 이런 리스크를 안더라도 끔으로써 얻는 이득이 매우 크니깐)

 

 

 

  /// WAS pre-fork 방식, copy

반응형