MyGPT - QtWebEngineProcess 메모리 누수 원인을 찾아서...

MyGPT를 오래 켜놓고 쓰다 보면 어느 순간부터 시스템이 묘하게 무거워졌다. 처음에는 ChatGPT 웹 자체가 무거운가 보다 하고 넘겼는데, Activity Monitor를 확인해보니 QtWebEngineProcess가 비정상적으로 커지고 있었다. 평소처럼 며칠씩 사용했을 때는 9GB에 육박한 적도 있었고, 이 정도면 단순히 “Chromium 계열이라 원래 많이 먹는다”는 말로 넘기기 어려웠다.

 

우연인지 확인하려고 일요일부터 MyGPT를 다시 평소처럼 사용했다. 긴 대화방을 오가고 과거 내용을 보기 위해 위로 스크롤하면서 계속 작업했는데, 하루 조금 지난 월요일 저녁에는 QtWebEngineProcess가 이미 3.93GB까지 올라가 있었다. 이번에는 앱을 재시작해서 숫자를 지우지 않고, 실제로 어디에서 메모리가 붙잡혀 있는지 확인해보기로 했다.

QtWebEngineProcess 9GB, 진짜 문제의 시작

처음 문제를 의심했을 때도 QtWebEngineProcess 하나가 이미 2GB를 넘기고 있었다. 내 사용 패턴상 긴 대화방을 자주 오가고 과거 내용을 많이 불러오기 때문에, 처음에는 대화가 길어질수록 DOM이나 JavaScript heap이 계속 쌓이는 쪽을 의심했다. 하지만 다른 대화방으로 이동하고 과거 메시지를 추가로 로딩한 뒤 Codex로 다시 계측해보니 메모리는 거의 늘지 않았다. 적어도 “긴 대화를 많이 불러오면 바로 메모리가 치솟는다”는 단순한 설명은 맞지 않았다.

다음으로는 가장 흔한 방법인 ⌘+R을 시험했다. 내가 MyGPT에서 새로고침을 실행하고 Codex가 전후의 renderer PID와 physical footprint를 비교했는데, 약 1,839MB였던 값이 오히려 1,860MB로 조금 늘었고 PID도 그대로였다. 여기서 일반 reload만으로는 비대해진 renderer를 정리할 수 없다는 점은 분명해졌다.

QWebEnginePage가 남아 있었다

다음 실험 방향은 ChatGPT와 함께 정했다. 로그인 session을 가진 profile은 유지하고 Page만 새로 만들면 비대해진 renderer를 새로 시작할 수 있는지 확인해보기로 했다. 실제로 새 Page에는 약 407MB 수준의 새로운 renderer가 붙었지만, 기존 3.97GB renderer는 그대로 살아 있어서 오히려 두 renderer가 동시에 존재하는 상태가 됐다.

 

이후 Codex가 Page lifecycle을 계측한 결과, 새 Page로 교체한 뒤에도 기존 QWebEnginePage가 완전히 파기되지 않고 남아 있다는 점이 확인됐다. 그래서 renderer를 직접 종료하는 쪽이 아니라, old Page의 lifetime을 정상적으로 끝내는 방법을 검토하는 방향으로 좁혔다.

deleteLater()와 WebEngine Reset

Codex 쪽 구현에서는 새 Page를 연결한 직후 old Page를 바로 없애지 않고, 다음 event loop에서 현재 Page가 아닌 것을 확인한 뒤 deleteLater()로 정리하도록 변경했다. 이후 old Page가 실제로 제거되면서 기존 renderer PID도 함께 종료됐고, MyGPT 프로세스와 로그인 session은 그대로 유지됐다. 특정 Chromium 내부 누수의 원인을 찾아 수정한 것은 아니지만, 비대해진 renderer를 앱 전체 재시작 없이 정리할 수 있는 방법은 확보했다.

 

이 동작을 MyGPT 0.0.5의 정식 기능으로 정리하면서 일반 reload와 WebEngine reset의 역할도 분리했다. ⌘+R은 기존 Page와 renderer를 유지하는 일반 reload로 남기고, ⌥+⇧+⌘+R은 현재 URL과 zoom factor를 보존한 뒤 새 Page를 만들고 old Page를 정리해 renderer를 교체하는 방식으로 구성했다.

항목 Reset 전 Reset 후
 MyGPT PID 51165 51165
 Renderer PID 51179 51985
 Renderer physical footprint 742MB 518MB
 Old renderer 실행 중 종료

runtime acceptance에서는 로그인/session, 기존 대화 목록, 현재 URL, 대화 복귀, prompt 입력과 메시지 전송, navigation, dynamic title, clipboard permission이 유지되는 것을 확인했다. PR review 과정에서는 reset 뒤 zoom factor가 초기화될 수 있다는 지적도 나와, 사용자가 조정한 zoom 상태까지 복원하도록 보완했다.

MyGPT 0.0.5

이번 작업으로 ⌥+⇧+⌘+R WebEngine reset을 추가했다. 실행하면 현재 URL과 로그인 session을 유지한 채 새 Page와 renderer를 만들고, 기존 Page와 renderer를 정리한다. 일반 ⌘+R은 기존 reload 동작으로 그대로 남겨뒀다. 장시간 사용에서 QtWebEngineProcess가 왜 몇 GB까지 증가하는지 QtWebEngine 내부 원인까지 규명한 것은 아니다. 다만 메모리가 비대해졌을 때 MyGPT를 완전히 종료하지 않고 renderer를 새로 시작할 수 있는 방법은 마련했다.

 

0.0.5에서는 자동 메모리 감시나 threshold 기반 reset도 넣지 않았다. 필요할 때 사용자가 직접 WebEngine reset을 실행하는 방식으로 두었고, 당분간은 다시 평소처럼 오래 사용하면서 실제 사용에서도 잘 버티는지 지켜볼 생각이다.