Sandcorp샌드코프 · 웹·AI 제품 개발

새 프로젝트 받는 중 · 갱신 2026.11sandcorp.co.krGitHub

← 기록

Windows핸들 누수성능운영디버깅

Windows가 사흘 만에 느려졌다 — 매일 찍은 자원 스냅샷으로 핸들 누수를 찾은 기록

켜 둔 지 며칠 지나면 느려지는 Windows 작업용 PC에서 매일 같은 시각 자원 스냅샷을 찍었습니다. 핸들이 하루 21만 개꼴로 늘고 있었고, 프로세스별 차이로 채팅 앱 하나와 원격 데스크톱 서비스를 찾아 재부팅 없이 회수했습니다. 같이 오르던 커밋 메모리는 원인이 따로 있었습니다.

Windows가 사흘 만에 느려졌다

매일 찍은 자원 스냅샷으로 핸들 누수를 찾은 기록

배경 — 「일주일쯤 켜 두면 느려진다」

샌드코프의 AI 작업 세션 여러 개는 Windows 작업용 PC 한 대에서 돕니다. 브라우저, AI 데스크톱 앱, WSL과 Docker, 가상 머신, 화상회의와 방송 프로그램까지 늘 함께 떠 있는 PC입니다. 이 PC는 끄지 않고 며칠씩 켜 둡니다.

문제는 「일주일쯤 켜 두면 작업 관리자의 CPU가 꾸준히 50%에 머문다」는 것이었습니다. 재부팅하면 나아지니 그때그때 재부팅으로 넘겼지만, 무엇이 쌓이는지는 몰랐습니다. 느려졌을 때 한 번 들여다보는 것으로는 원인을 가리기 어렵습니다. 그 순간 무엇을 돌리고 있었는지에 따라 숫자가 크게 출렁이기 때문입니다.

그래서 2026년 11월 2일 11시 19분에 재부팅해 기준점을 잡고, 매일 같은 시각에 같은 방식으로 자원 스냅샷을 찍어 날짜끼리 비교하기로 했습니다. 이 글은 사흘 동안의 기록으로 범인을 찾은 과정입니다.

스냅샷을 이렇게 찍었습니다

작업 스케줄러에 PowerShell 스크립트 하나를 매일 12시에 돌게 걸었습니다. 놓친 실행은 PC가 깨어나면 바로 돌게 했습니다. 스크립트는 한 번에 두세 분 동안 표본을 모아 다음을 파일로 남깁니다.

  • 시스템 전체 지표: CPU(사용자 모드·커널 모드 따로), 커밋 메모리, 페이징·비페이징 풀, 핸들·프로세스·스레드 수의 평균·최소·최대
  • 프로세스별 표: 이름·개수·Private 메모리·핸들·스레드·누적 CPU 시간
  • 표본 동안 CPU 상위 프로세스

값은 성능 카운터 경로 대신 CIM 성능 클래스로 읽었습니다. 한국어 Windows에서는 성능 카운터 이름이 번역돼 있어서, 영어 이름으로 짠 스크립트가 다른 PC에서 그대로 돌지 않기 때문입니다. 비교 스크립트는 가장 오래된 기록과 가장 최근 기록을 나란히 놓고 차이를 표로 뽑습니다.

같은 시각에 찍는 것이 중요했습니다. 정오에는 대체로 비슷한 일을 하고 있어서, 날짜끼리의 차이가 「그날 무엇을 했나」보다 「며칠 켜 두었나」를 더 많이 반영합니다.

범인을 좁혀 간 길

  1. 전체 지표

    CPU는 날마다 출렁였지만 핸들은 매일 한 방향으로 늘었다

  2. 커널 모드 비중

    사용자 모드보다 커널 모드 CPU가 더 컸다

    단서
  3. 프로세스별 차이

    날짜끼리 빼 보니 채팅 앱과 svchost 하나가 남았다

  4. svchost 안의 서비스

    PID로 서비스를 찾으니 원격 데스크톱 서비스

    범인 둘
  5. 그것만 재시작

    재부팅 없이 핸들 55만 개 회수

무엇이 문제였나

핸들이란

프로그램이 파일, 레지스트리 키, 이벤트, 스레드, 소켓 같은 커널 객체를 쓰려면 커널에 열어 달라고 하고, 커널은 그 객체를 가리키는 번호표(핸들) 를 돌려줍니다. 다 쓰면 번호표를 반납해야 커널이 객체를 치웁니다. 반납하지 않으면 객체가 커널 메모리에 남고, 커널은 그만큼 더 많은 객체를 관리하게 됩니다. 프로그램 하나가 수십만 개를 쥐고 있어도 화면에는 아무 표시가 없습니다.

증상 — 핸들만 한 방향으로 늘었습니다

부팅 직후 시스템 전체 핸들은 19.5만 개였습니다. 하루 뒤 52.5만, 이틀 뒤 70.2만, 사흘 뒤 81.9만이었습니다. 사흘 동안 하루 평균 21만 개꼴로 늘었습니다. 같은 기간 스레드는 8천 개에서 1.8만 개로, 프로세스는 약 420개에서 540개로 늘었습니다. 페이징 풀은 0.9GB에서 2.3GB로, 비페이징 풀은 1.0GB에서 1.75GB로 커졌습니다.

CPU도 올랐습니다. 커널 모드 CPU는 기준점 18.6%에서 하루 뒤 29.5%, 이틀 뒤 32.6%였습니다. 기준점에서도 이미 커널 모드가 사용자 모드(14.5%)보다 컸고, 그 차이가 날마다 벌어졌습니다. 앱이 일을 많이 해서가 아니라 커널이 관리할 것이 많아져서 바쁘다는 신호였습니다.

버린 가설 — CPU 상위 프로세스를 보면 나온다

처음에는 작업 관리자처럼 CPU를 많이 쓰는 프로세스를 보면 범인이 나올 줄 알았습니다. 사흘째 표본에서 CPU 상위 10개 프로세스를 다 더해도 16% 남짓이었습니다. 1위는 방송 프로그램(5.1%), 2위는 게임(3.1%)이었고, 둘 다 그 시각에 일부러 켜 둔 것이었습니다. 커널 모드 CPU 22.5%를 설명할 프로세스는 목록에 없었습니다. 커널이 바쁜 시간은 그 원인이 된 프로세스의 몫으로 잡히지 않기 때문입니다.

CPU 숫자 자체도 믿을 만한 지표가 아니었습니다. 이틀째 정오에는 전체 CPU가 60.9%였는데, 핸들이 더 많은 사흘째 정오에는 38.5%였습니다. 그 시각에 무엇을 돌리고 있었는지가 누수보다 더 크게 숫자를 흔들었습니다. 그래서 CPU 대신 며칠 내내 한 방향으로만 움직이는 값인 핸들을 따라가기로 했습니다.

원인 — 두 프로세스가 반납하지 않고 있었습니다

프로세스별 핸들 표를 날짜끼리 빼 보니, 늘어난 양 대부분이 두 곳에 몰려 있었습니다.

  • 채팅 앱: 첫날 기록의 핸들 상위 목록에는 없다가, 하루 뒤 9.8만, 이틀 뒤 19.4만, 사흘 뒤 29.2만이었습니다. 하루 약 9.7만 개씩 거의 일정하게 늘었고, 같은 기간 스레드도 2.5천 개에서 7.2천 개로 늘었습니다. 무언가를 할 때 튀는 것이 아니라 시간이 지나면 쌓이는 모양입니다.
  • svchost 하나: Windows 서비스 여러 개를 담는 svchost 가운데 하나가 부팅 직후 1,600개에서 하루 뒤 18만 개로 뛰고, 그 뒤 21.8만 개에서 멈췄습니다. svchost는 이름만 보면 무슨 서비스인지 알 수 없어서, 프로세스 ID로 그 안에 든 서비스를 찾았습니다. 원격 데스크톱 서비스였습니다. 첫날에만 크게 늘고 멈춘 이유는 기록만으로는 알 수 없었습니다.

사흘째 기준으로 둘을 더하면 51만 개로, 기준점 대비 늘어난 62만 개의 약 80%였습니다.

고른 방법과 이유

재부팅 대신 그 둘만 다시 띄웠습니다

재부팅하면 확실히 풀리지만, 이 PC에는 몇 시간짜리 AI 작업 세션과 가상 머신이 떠 있어서 재부팅은 그 작업을 모두 끊습니다. 범인을 알았으니 그것만 내리기로 했습니다.

  • 채팅 앱은 창을 닫는 데서 그치지 않고 프로세스를 완전히 끝낸 뒤 다시 켰습니다. 29.2만 개가 5,800개가 됐습니다.
  • 원격 데스크톱 서비스는 그 서비스만 다시 시작했습니다. 21.8만 개가 500개가 됐습니다. 이 서비스를 다시 시작하면 원격 접속 중인 세션이 끊기므로, 원격으로 붙어 있지 않을 때 해야 합니다.

시스템 전체 핸들은 81.9만에서 26.2만으로 줄었습니다. 재시작 직후 잰 커널 모드 CPU는 22%대에서 12% 안팎으로 내려왔습니다.

같이 오르던 커밋 메모리는 따로 놀았습니다

같은 사흘 동안 커밋 메모리도 46GB에서 74GB로 올랐습니다. 핸들과 나란히 오르니 같은 누수의 결과처럼 보였습니다. 그런데 핸들을 55만 개 회수한 뒤에도 커밋 메모리는 69GB였습니다. 핸들이 68% 줄 때 커밋은 6% 줄었습니다.

커밋을 차지한 쪽을 보니 AI 데스크톱 앱(6.5GB), 브라우저(5.7GB), WSL(4.4GB), 가상 머신(4.1GB)이었습니다. 사흘 사이에 AI 데스크톱 앱 프로세스는 3개에서 19개로 늘었고, 첫날에는 메모리를 거의 잡지 않던 가상 머신이 4GB 넘게 잡고 있었습니다. 누수가 아니라 그동안 띄워 둔 것이었습니다. 가상 머신과 WSL은 핸들 수로는 0~1개로 잡히니, 핸들 표만 봤다면 이 몫을 놓쳤을 것입니다.

결과

  • 범인 둘(채팅 앱, 원격 데스크톱 서비스)을 찾았고, 재부팅 없이 그 둘만 다시 띄워 핸들 55만 개를 회수했습니다.
  • 「일주일쯤 켜 두면 느려진다」던 증상은 사흘치 기록에서 이미 하루 21만 개꼴의 핸들 증가로 드러나 있었습니다.
  • 커밋 메모리가 오른 것은 별개였습니다. 띄워 둔 AI 세션·브라우저·가상 머신·WSL의 몫이라, 줄이려면 「무엇을 덜 띄울지」를 정해야 하는 문제입니다.
  • 스냅샷은 그대로 매일 돌고 있습니다. 앞으로는 프로세스 하나의 핸들이 하루에 몇만 개씩 늘면 그것부터 의심합니다.

배운 것

  • 범인을 찾은 것은 비싼 도구가 아니라 매일 같은 시각에 남긴 표 여섯 장이었습니다. 하루치 표로는 아무것도 보이지 않았고, 표끼리 뺐을 때 비로소 보였습니다.
  • svchost는 이름만으로는 아무것도 알려 주지 않습니다. 프로세스 ID로 그 안의 서비스를 찾는 한 단계가 있어야 「Windows가 느리다」가 「원격 데스크톱 서비스가 핸들을 쥐고 있다」로 바뀝니다.
  • 범인을 알면 재부팅까지 갈 필요가 없습니다. 이 PC처럼 긴 작업이 늘 떠 있는 환경에서는 그 차이가 큽니다.
  • 그럴듯한 연결(핸들과 메모리가 함께 오름)은 고쳐 보기 전까지 가설일 뿐이었습니다.

남은 한계

  • 왜 쌓이는지는 모릅니다. 채팅 앱과 원격 데스크톱 서비스가 어떤 객체를 반납하지 않는지까지는 들여다보지 않았습니다. 지금 할 수 있는 것은 주기적으로 다시 띄우는 것뿐이고, 앱이 업데이트되면 증상이 바뀔 수 있습니다.
  • CPU 효과는 흔들립니다. 재시작 직후 커널 모드 CPU는 12% 안팎이었지만, 얼마 뒤 다른 작업이 돌 때 잰 10초 표본에서는 27%가 나오기도 했습니다. 효과는 CPU보다 핸들 수로 판단하고, 다음 날 12시 스냅샷으로 다시 비교해야 합니다.
  • 풀 메모리는 거의 그대로였습니다. 핸들이 크게 줄었는데도 페이징 풀은 2.3GB 남짓으로 큰 변화가 없었습니다. 어디에 남아 있는지는 아직 확인하지 못했습니다.
  • 하루 한 번 찍는 기록입니다. 하루 안에서 언제 늘었는지, 어떤 작업과 같이 늘었는지는 이 간격으로는 알 수 없습니다. 원격 데스크톱 서비스가 첫날에만 크게 늘고 멈춘 이유도 그래서 모릅니다.

문의 →