[IT 심층 분석]
RAW 단일 이미지 대량 처리 중 VRAM 메모리 누수 원인과 해제 로직 방어 구축
김민준 · IT 시스템 엔지니어|
전문 포토그래퍼를 위한 고해상도 이미지 일괄 처리 서버를 개발하는 도중 클라우드 인프라 파트에서 알람이 빈번하게 울리는 사태가 발생했습니다. GPU 자원을 활용해 RAW 형식의 이미지 데이터들을 DNG 포맷으로 컨버팅하는 병렬 배치 프로세서가 주기적으로 OOM(Out of Memory) 크래시를 내보내며 파드가 재시작되는 현상이었습니다. 단순히 트래픽 폭주로 인한 자원 고갈 현상이었다면 인스턴스를 스케일 아웃하는 것으로 넘길 수 있었겠지만 문제는 VRAM 메모리 지표가 우상향 그래프를 그리는 명백한 메모리 누수 패턴을 띠고 있었다는 점입니다. 이를 방치하면 시스템의 물리적 운영 비용이 기하급수적으로 부풀어 오를 터이기에 즉각 추적에 착수했습니다.
GPU 기반 영상 처리 파이프라인에서 메모리 누수가 발생하면 일반적인 CPU 단의 메모리 프로파일링 툴보다는 네이티브 드라이버 단의 전문 도구가 필요하기에 엔비디아의 프로파일러를 물려 프로세스를 1단계부터 스텝별로 다시 실행시켜보았습니다. 수백 메가바이트에 달하는 14비트 센서 RAW 데이터가 GPU 디바이스 메모리로 전송되고 이를 딥러닝 디모자이킹 알고리즘에 통과시킨 뒤 결과물을 호스트 메모리로 내보내는 과정이었습니다. 분명 표면상의 파이썬 래퍼 스크립트에서는 객체의 레퍼런스 카운트를 철저하게 관리하고 종료 시 명시적으로 가비지 컬렉터의 호출까지 염두에 둔 코드가 자리잡고 있었습니다. 그러나 C++ 단으로 바인딩된 백엔드 코어 모듈을 조사하자 놀라운 곳에서 해제 루틴이 증발해버린 사실을 발견할 수 있었습니다.
에러의 발단은 예외 처리 블록의 설계 결함이었습니다. 아주 간헐적으로 특정 회사의 비표준 RAW 파일을 만날 경우 메타데이터 파싱 로직에서 무시 가능한 수준의 경고 예외가 발생하곤 했습니다. 그런데 이 예외를 처리하는 구문이 제어 흐름을 반환할 때 GPU에 미리 할당해놓은 텍스처 버퍼나 스크래치 메모리에 도달하지 못한 채 상위 스택으로 컨텍스트를 탈출해버리는 것이었습니다. GPU VRAM 상에 할당된 포인터는 호스트 운영체제의 가비지 컬렉터가 수집할 수 없는 영역이므로 결국 해당 청크는 영구적인 데드 스페이스로 전락하게 되었습니다. 이러한 비정상적 경고 파일이 수천 개 쌓이다 보니 거대한 GPU 메모리라도 불과 몇 시간 내에 한도에 다다를 수밖에 없었지요.
이 문제를 우회하고 원천적으로 차단하기 위해 저는 메모리 관리 기법의 꽃이라 불리는 RAII(Resource Acquisition Is Initialization) 패턴을 네이티브 코드 계층에 전면 도입했습니다. C++의 스마트 포인터를 응용하여 GPU 메모리 할당과 해제를 클래스의 객체 수명 주기에 완전히 종속시켰습니다. 생성자에서 디바이스 메모리를 잡아두고 소멸될 때 어떠한 예외 상황이 터지더라도 반드시 해제 API를 호출하도록 설계한 것입니다. 추가적으로 호스트 프로세스가 GPU 드라이버 API를 호출할 때 비동기 스트림이 완전히 종료될 때까지 대기 동기화를 맞추어 예기치 못한 비동기 콜백 미아 상태를 방지하는 방어 로직까지 구현했습니다.
버그 픽스 코드를 서버 팜에 무중단 배포한 후 스트레스 테스트를 재개했습니다. 일주일에 달하는 롱런 테스트 동안 무려 오백만 장 이상의 손상된 더미 RAW 이미지를 일부러 주입하며 결과를 감시했으나 VRAM 사용량 그래프는 더 이상의 상승세 없이 철저하게 일정 수준을 유지하며 아름다운 잔물결 형태를 보였습니다. 저수준 언어와 하드웨어 드라이버를 직접 핸들링하는 영역에서는 단 한 줄의 예외 처리 누락도 시스템 전체를 침몰시키는 폭뢰가 될 수 있습니다. 현엽에서의 트러블슈팅은 이처럼 프레임워크가 가려놓은 장막을 걷어내고 메모리 포인터 한 땀 한 땀을 직접 응시할 때 비로소 진정한 해답이 나타남을 상기시켜 줍니다.