가비지 컬렉터, 왜 알아야하는가 - 3. 파이썬의 가비지 컬렉터

삼콩·2023년 4월 21일
0

가비지 컬렉션

목록 보기
3/4

본 시리즈는 팀 내 작디 작은 세미나에서 발표했던 내용으로 이루어져 있습니다.
우아한 테코톡의 던님의 영상Aaron Yoo 유튜버의 영상 내용을 주로 참고했습니다!
오류가 있다면 댓글로 남겨주시면 감사하겠습니다!
(필자는 파알못이기 때문에 틀린 부분이 있을 수 있습니다!)

이번 글에서는 파이썬에서 가비지 컬렉터가 어떤 방식으로 구현되어있는지 살펴보도록 하겠습니다. 실제로 인스타그램에서 파이썬의 가비지 컬렉션을 비활성화했는데요, 어떤 방식으로 구현되어있는지를 살펴보며 인스타그램이 어떤 방식을 통해 비활성화 했는지를 알아보겠습니다.

파이썬은 참조 카운트(reference counting)와 세대별 가비지 컬렉션(generational garbage collection)을 사용하여 가비지 컬렉션을 수행합니다. 참조 횟수를 증가시키는 방법은 변수에 객체를 할당하는 방법, list에 추가하거나 class instance에서 속성으로 추가하는 등의 data structure에 객체를 추가하는 방법 그리고 객체를 함수의 인수로 전달하는 방법이 있습니다.

Python standard library의 sys 모듈을 사용하면 특정 객체의 reference counts(참조 횟수)를 확인할 수 있는데요.


이 코드를 보고 참조 카운팅이 몇으로 되었는지 맞춰보실 수 있나요? 2입니다. 변수를 생성할 때 1이 올라가고 a를 getrefcount함수로 전달할 때 1이 올라갑니다.

Python은 메모리 관리를 위한 reference counting 외에도 generational garbage collection(세대별 가비지 컬렉션)이라는 방법을 사용하는데요. 우리가 앞에서 봤듯 참조 카운팅의 경우 순환 참조 문제가 발생하기 때문에 파이썬은 이 문제를 세대별 가비지 컬렉션으로 해결합니다. 파이썬의 세대별 가비지 컬렉터는 내부적으로 generation(세대)과 threshold(임계값)로 가비지 컬렉션 주기와 객체를 관리합니다. 세대는 0~2세대로 구분되고 최근 생성된 객체는 0세대(young)에 들어가고 오래된 객체일수록 1세대와 2세대(old)로 이동합니다. 당연히 한 객체는 단 하나의 세대에만 속하게 되구요.

가비지 컬렉터는 0세대일수록 더 자주 가비지 컬렉션을 하도록 설계되어있는데 이 또한 약한 세대 가설을 따르도록 설계가 된것이죠. 만약 0세대에서 수명이 오래되는 객체가 있다면, 해당 객체는 1세대로 이동합니다. 1세대에서는 mark-and-sweep 알고리즘을 사용하여 가비지 컬렉션을 수행합니다. 이러한 방식으로, 0세대에서 순환 참조가 발생하더라도 1세대로 넘어갈 때 메모리 누수를 방지하게됩니다.

각 세대마다 가비지 컬렉터 모듈에는 임계값 개수가 있는데요. 객체 수가 해당 임계값을 초과하면 가비지 콜렉션이 콜렉션 프로세스를 추가 합니다. 해당 프로세스에서 살아남은 객체는 다른 세대로 옮겨지게 되죠. 자바의 age와 비슷한 개념입니다.

파이썬에서는 세대 가비지 컬렉터의 동작을 변경할 수 있다는 점도 특이한데요. garbage collection process를 trigger 하기 위한 임계값 변경, garbage collection process(가비지 컬렉션 프로세스)를 수동으로 trigger 하거나, garbage collection process(가비지 컬렉션 프로세스)를 모두 비활성화할 수 있습니다. gc 모듈을 사용하여 가비지 컬렉션 통계를 확인하거나 가비지 컬렉터의 동작을 변경하는 방법에 대해 살펴보겠습니다.


gc를 import하고 get threshold함수를 사용하면 각각 0,1,2세대에 임계값이 나옵니다. 0세대에서 객체를 할당한 횟수가 700번을 초과하면 가비지 컬렉션이 수행된다는 거죠.

get_count() method를 사용하여 각 세대의 객체 수를 확인할 수도 있습니다. 위 예에서는 youngest generation(가장 어린 세대)에 121개의 객체, 다음 세대에 9개의 객체 oldest generation(가장 오래된 세대)에 2개의 객체가 있다는걸 확인할 수 있네요.

메모리를 확보하기 위해 수행하는 수동 가비지 컬렉션 프로세스는 원하지 않는 결과가 나올 수 있는데요, 위 경고를 무시하고 가비지 컬렉션 프로세스를 관리하려는 경우가 종종 있습니다. 그것이 인스타그램의 경우인데요, Instagram 개발팀은 모든 세대의 임계값을 0으로 설정하여 비활성화했습니다. 이 변경으로 인해 웹 응용 프로그램이 10% 더 효율적으로 실행되는 좋은 결과를 얻었다고 합니다. Instagram의 서버는 자식 프로세스가 마스터와 메모리를 공유하는 마스터 차일드 메커니즘을 사용되어 실행된다고 합니다.

이 과정들은 중요한게 아니고, 결론적으로 인스타그램에서는 자식 프로세스가 생성된 직후에 공유 메모리가 급격히 떨어진다는 사실을 발견하게 됐고 해당 이슈를 추적하다보니 이것은 파이썬 gc 모듈의 gc.collect()가 수행될 때의 문제로 밝혀졌습니다. 이 gc collect는 특정 임계값에 도달했을 때 수행되기 때문에 이 gc.collect의 수행을 임계치를 0으로 하는 것으로 막아 문제를 해결했다고 합니다. 하지만 Insgargm의 웹 어플리케이션 규모는 수백만 명이 사용하니까 어떤 방법을 사용해서든 웹 응용 프로그램의 모든 성능을 한계치로 끌어 올리는 것이 좋겠지만 저희와 같은 대부분의 개발자에게는 가비지 컬렉션과 관련된 Python의 표준 동작이 충분할 것 같습니다.

참고한 글
https://luavis.me/python/dismissing-python-garbage-collection-at-instagram
https://medium.com/dmsfordsm/garbage-collection-in-python-777916fd3189

profile
프론트엔드 세계의 모략을 꾸미는 김삼콩입니다

0개의 댓글