-
Django Application 의 메모리 누수 해결하기기술/Django 2022. 1. 25. 20:00
원본: https://adamj.eu/tech/2019/09/19/working-around-memory-leaks-in-your-django-app/
Working Around Memory Leaks in Your Django Application - Adam Johnson
Working Around Memory Leaks in Your Django Application 2019-09-19 Several large Django applications that I’ve worked on ended up with memory leaks at some point. The Python processes slowly increased their memory consumption until crashing. Not fun. Even
adamj.eu
내가 작업한 여러 대형 Django 응용 프로그램은 어느 시점에서 메모리 누수로 종료되었습니다. Python 프로세스는 메모리 에러가 발생할 때까지 메모리 소비를 천천히 늘어갔습니다.. 프로세스를 자동으로 다시 시작해도 여전히 약간의 가동 중지 시간이 있었습니다.
Python의 메모리 누수는 일반적으로 무한대로 커지는 모듈 수준 변수에서 발생합니다. 이것은 최대 크기가 무한대인 lru_cache 이거나 실수로 잘못된 범위에서 선언된 단순 리스트일 수 있습니다.메모리 누수는 자신의 코드 범위에서만 일어나지 않습니다. Buzfeed 에 쓴 Peter Karp 의 훌륭한 예시에서 파이썬 표준 라이브러리에서 하나를 찾았습니다.
해결 방법
아래 해결 방법은 모두 많은 요청 또는 작업 후에 Worker 프로세스를 다시 시작합니다. 잠재적으로 무한히 누적되는 Python 객체를 제거하는 간단한 방법입니다. 웹 서버, Celery 또는 이와 유사한 기능이 있지만 기능이 없는 경우 알려주시면 추가하겠습니다!지금 당장 메모리 누수가 발생하지 않더라도 이를 추가하면 애플리케이션의 복원력이 높아집니다.Gunicorn
Gunicorn 을 Python 웹 서버로 사용하는 경우 `--max-requests` 설정을 사용하여 주기적으로 Worker 프로세스를 재시작할 수 있습니다. 동시에 `max-requests-jitter` 를 사용하여 모든 Worker 프로세스가 동시에 재시작되지 않도록 설정합니다. 이렇게 하면 Worker 프로세스의 시작 부하를 줄일 수 있습니다.
예를 들어, Gunicron 프로젝트는 이렇게 설정했습니다.
gunicorn --max-requests 1000 --max-requests-jitter 50 ... app.wsgi
프로젝트의 트래픽 수준, Worker 수 및 서버 수에 대해 약 1.5 시간마다 Worker를 재시작했습니다. 5% jitter 는 재시작 부하를 상쇄하기 충분했습니다.
uWSGI
uWSGI를 사용하는 경우 유사한 max-requests 설정을 사용할 수 있습니다. 이렇게 하면 수많은 요청 후에 Worker가 다시 시작됩니다.예를 들어, 이전 프로젝트에서 저는 uwsgi에서 이 설정을 사용했습니다.[uwsgi] master = true module = app.wsgi ... max-requests = 500
uWSGI도 jitter를 추가하기 위한 max-requests-delta 설정을 제공합니다. 하지만 고정값이기 때문에 Gunicorn보다 구성하기가 더 귀찮습니다. Worker 수나 max-request 값을 변경할 경우 max-requests-delta를 다시 계산하여 jitter를 일정 비율로 유지해야 합니다.Celery
Celery 는 메모리 누수에 대해 몇가지 다른 설정을 제공합니다.
먼저 worker_max_tasks_per_child 설정이 있습니다. 이렇게 하면 Worker 하위 프로세스가 많은 태스크를 처리한 후 다시 시작됩니다. 지터에 대한 옵션은 없지만 셀러리 작업은 실행 시간이 긴 경향이 있으므로 자연스럽게 jitter가 있을 것입니다.예를 들어:app = Celery("myapp") app.conf.worker_max_tasks_per_child = 100
Django 를 사용한다면:
CELERY_WORKER_MAX_TASKS_PER_CHILD = 100
100개의 작업 후 종료하게 하는게 위의 웹 요청 설정에 비해 적습니다. 과거에 저는 백그라운드 작업에서 더 많은 메모리를 소비를 보았기 때문에 셀러리에 더 작은 값을 사용하게 되었습니다. ( Celery 자체에서도 메모리 누수가 있었다고 생각합니다. )사용할 수 있는 다른 설정은 worker_max_memory_per_child 입니다. 이 값은 Worker를 대체하기 전에 사용할 수 있는 최대 메모리 킬로바이트를 지정합니다. 조금 더 복잡해서 써보지 않았습니다.만약 당신이 worker_max_memory_per_child를 사용한다면, 당신은 아마도 당신의 총 메모리의 백분율로 계산해야 할 것입니다.. 이런 방식으로 하위 프로세스 수 또는 서버의 사용 가능한 메모리를 변경하면 자동으로 확장됩니다. 예(검증되지 않음):import psutil celery_max_mem_kilobytes = (psutil.virtual_memory().total * 0.75) / 1024 app.conf.worker_max_memory_per_child = int( celery_max_mem_kilobytes / app.conf.worker_concurrency )
psutil을 사용하여 총 시스템 메모리를 가져옵니다. 이후 Celery에 최대 75%(0.75)를 할당합니다.'기술 > Django' 카테고리의 다른 글
일반적인 Django 서비스는 단일 앱을 통해 개발해야합니다. (0) 2022.12.11 Django 에서 N+1 쿼리 문제 예방, 발견하기 (0) 2022.01.25 'Settings' object has no attribute 'worker_state_db' (0) 2021.09.25 Django Rest Framework 의 APITestCase 에서 API 를 요청할 때 Body 데이터를 보내는 두가지 방법 (0) 2021.09.24 Django 에서 Jupyter Notebook 을 사용할 때 서브 디렉터리를 기본으로 설정하는 방법 (0) 2021.09.24