Python의 GC가 멀티프로세싱과 만나면 생기는 일 - Python과 Airflow, 그리고 관련된 문제 해결기 2편
두줄요약
Airflow의 fork 기반 프로세스에서 Python 순환 GC가 COW와 MySQL 커넥션 종료를 유발한 원인을 분석했습니다. Memray로 별도 누수를 수정하고, 지연 import로 fork 승계 객체를 줄이는 접근을 다뤘습니다.
문제 상황
- Airflow의 fork 기반 worker에서 RSS 변화 없이 PSS·USS만 지속 증가하는 메모리 사용량 상승
- MySQL 환경의 dag-processor에서 부모 DB 커넥션 종료 후 쿼리 실패와 불규칙한 재시작
- Memray로 확인한 불필요한 kubernetes.client import 및 SSL context 반복 생성 문제
원인 분석
- fork 이후 자식의 순환 GC가 부모에게서 승계한 추적 객체의 PyGC_Head에 기록하며 COW 유발
- SQLAlchemy 풀과 커넥션 레코드의 순환 참조가 자식 GC 시점에 수거되며 mysqlclient의 COM_QUIT 전송
- 작업 시간이 길고 객체 할당이 많을수록 GC 임계값 도달 가능성 증가
구조와 흐름
- fork 직후 부모·자식의 물리 페이지 공유와 양쪽 PTE 쓰기 금지 상태
- 자식의 gc.collect()만으로 객체가 놓인 공유 페이지의 PFN 분리 확인
- 자식 시작 시 engine.dispose(close=False) 이후에도 순환 참조 객체의 지연 수거 가능성
해결 방법
- Memray 기반 분석으로 secrets masker의 불필요한 모듈 import와 Task SDK client의 SSL context 누수 수정
- worker 생성 이후 무거운 모듈을 지연 import하여 fork 시 승계 객체량 축소
- 주요 컴포넌트의 Memray 추적 실행 기능과 메모리 프로파일링 문서 추가

