
가비지 컬렉터의 보이지 않는 쓰기 동작과 Copy-on-Write의 한계를 끝까지 추적한 깊이 있는 분석
본 아티클은 Airflow의 멀티프로세싱(fork) 환경에서 발생하는 심각한 두 가지 오류의 실태와 이를 완벽하게 분석해 나가는 과정을 다룹니다. CPython의 순환 참조 GC가 의도치 않게 Copy-on-Write를 자극하여 부모의 공유 메모리를 파괴하는 기전과, 자식 프로세스가 가비지를 해제할 때 부모의 DB 커넥션까지 유실시키는 미스터리를 원리 수준에서 파고듭니다. 저자가 실제로 모니터링 도구와 가상 메모리 영역 분석을 통해 원인을 규명하고 글로벌 오픈소스에 직접 패치를 보낸 생생한 노하우가 담겨 있습니다.
대규모 트래픽을 처리하며 Python 멀티프로세싱 환경을 고도화하고 있거나, Airflow 등 오케스트레이션 툴의 메모리 비대화 및 알 수 없는 DB 연결 종료 문제로 고통받는 시니어 백엔드 아키텍트들에게 강력히 권장합니다.
Airflow PoC 환경에서 fork 방식을 사용함에 따라 두 가지 오동작이 발견되었습니다. 첫째는 worker 프로세스의 PSS 메모리가 누적 증가하는 현상이었고, 둘째는 MySQL 메타데이터 DB 환경에서 자식 프로세스가 종료될 때 부모 프로세스의 커넥션이 끊겨 dag-processor가 예외로 재시작되는 현상이었습니다.
분석 결과 두 문제 모두 CPython의 메모리 관리 및 가비지 컬렉션 방식에 연관되어 있음이 밝혀졌습니다. 자식 프로세스에서 가비지 컬렉션이 돌 때 부모로부터 승계된 객체의 헤더 영역에 쓰기 동작이 가해져 Copy-on-Write(COW)를 유발했고, SQLAlchemy의 풀에 잔존하던 순환 참조된 커넥션 레코드가 수거되는 과정에서 소켓 연결을 명시적으로 종료했음을 확인했습니다.
Memray 추적을 통해 SSL context 생성 누수 및 불필요한 전체 import 요소를 먼저 수정해 Airflow 3.1.2에 기여했습니다. 더불어 향후 사용자들이 동일 문제를 쉽게 예방할 수 있도록 Airflow 프레임워크 자체에 Memray 프로파일링 실행 기능과 공식 메모리 프로파일링 문서를 추가하는 성과를 냈습니다.
Trade-off
메모리 팽창 문제를 원천 방지하기 위해 fork 시점 전에 무거운 모듈 로드를 지연시키는 지연 로딩 방안을 모색하였으나, Airflow CLI 및 프레임워크 특성상 기본적으로 로드해야 하는 모듈이 많아 자식 프로세스의 최초 메모리 양을 대폭 줄이기엔 현실적인 한계가 존재했습니다.
자식 프로세스가 부모의 메모리 프레임을 물리적으로 복사하지 않고 가상 페이지 테이블만 공유하다가, 한쪽에서 데이터 쓰기가 일어날 때만 해당 페이지만 실제 복제하여 사용량을 절감하는 리눅스 메모리 관리 메커니즘입니다.
CPython의 순환 가비지 컬렉터가 컨테이너 객체의 순환 참조를 감지하고 추적하기 위해 객체 앞에 부착하는 16바이트 혹은 24바이트 크기의 추가적인 메모리 헤더입니다.
매 요청마다 데이터베이스 커넥션을 맺고 끊는 병목을 제거하기 위해 데이터베이스 연결 객체들을 풀 형태로 유지관리해 주는 SQLAlchemy 라이브러리의 핵심 기능입니다.




