ice rabbit programming

[회고] 이벤트 루프 소실 본문

Development/문제 진단 회고

[회고] 이벤트 루프 소실

판교토끼 2026. 9. 14. 17:41
728x90

들어가기 전에

AI 도구를 활용한 개발이 이제는 그 전이 까마득한 옛날이었던 것으로 느껴질만큼 상용화되었다. 다만 26년 중반을 넘어가는 현재 시점에서는 사용자의 능력에 따라서 이끌어낼 수 있는 결과물의 정도가 달라진다고 생각하고, 이슈가 있을 경우 이를 추론하고 판단하는 능력이 더욱 중요해졌다고 생각한다. 그렇기 때문에 6년 반 정도 개발자로 일하면서 자연스럽게 훈련된 프로세스인, 이슈를 찾고 원인을 파악해서 해결하는 과정을 조금씩 정리해보고자 한다.

대용량 diff가 웹소켓을 굶겨 죽였다 — 이벤트 루프 starvation

발단

빌드 업로드 클라이언트가 업로드까지 끝낸 뒤 원격 동기화 완료 로그와 read: connection closed normally를 남기고 간헐적으로 멈추는 현상. 그리고 결정적 제보 한 줄 — "기존 버전과 diff가 클 때(파일 수·용량) 잘 발생하는 것 같다."

파고들기

diff 크기와 상관관계가 있다는 건, 어딘가 크기·시간에 비례해 나빠지는 자원이 있다는 뜻이다. diff 엔진과 실행 모델을 따라갔다.

내부 바이너리 diff 엔진의 메모리 모델부터 문제였다. old 파일과 new 파일을 통째로 메모리에 올리고, 거기에 작업용 자료구조(서픽스 배열 등)를 또 쓴다. 이게 스레드 수만큼 동시에 돌면 메모리 사용량이 곱으로 뛴다. 멀티파트 대용량 릴리스에서는 파트당 최대 1.7~2 GB까지 잡는 경우가 관측됐다.

더 치명적인 건 실행 위치였다. 인코딩·압축·diff를 수행하는 로드 단계가 io 스레드 위에서 동기로 실행되고 있었다. 이 클라이언트의 io_context는 스레드 풀 하나를 웹소켓 세션과 요청 처리가 공유한다. 그런데 대용량 diff가 시작되면 그 무거운 작업이 io 워커 하나를 통째로, 그것도 수 분간 점유해버린다.

즉 diff가 클수록 이렇게 된다.

  1. io 워커 한 개가 diff에 오래 붙잡혀 있고
  2. RAM 압박까지 겹쳐 나머지 처리도 느려지고
  3. 그동안 메타데이터 관리 서버와의 커넥션은 30초 KeepAlive로만 겨우 살아 있는데
  4. 오래 열려 있을수록 서버가 동기화 단계 전환 근처에서 연결을 닫을 확률이 커진다 → connection closed normally → 완료 신호를 잃는 레이스에 명중 → hang

"그 머신에서만, diff가 클 때만" 재현되던 이유가 여기 다 있었다. 부하가 임계치를 넘는 특정 조합에서만 이벤트 루프가 굶어 죽었던 것이다.

곁가지로 하나 더 나왔다. diff 인코더의 헤더 처리 함수가 성공 경로에서 fclose를 빠뜨려 diff 1건당 파일 핸들을 누수하는 것을 바이너리 디스어셈블로 확인했다. 대량 파일을 도는 상황에서 메모리·핸들 압박을 같이 키우는 요인이었다.

해결

임시로 즉시 조치로 서버 웹소켓 idle 타임아웃을 넉넉히(15초 → 60초) 늘리고 KeepAlive 주기를 명시적으로 관리해, 오래 걸리는 로드 구간 내내 커넥션을 살렸다.

근본 해결은 파일 처리 단계를 io 스레드에서 분리하는 것이다. 별도 워커 또는 스트랜드로 옮겨 무거운 diff가 네트워크 I/O를 점유하지 못하게 하고, io 스레드는 I/O만 하도록 남긴다. 부수적으로 diff 엔진의 핸들 누수와 스레드당 메모리 상한도 함께 정리했다.

교훈

"크기와 상관있다"는 제보는 자원 고갈의 신호다. 시간·메모리에 비례해 악화되는 지점을 먼저 의심하자.

CPU·메모리를 크게 먹는 작업을 이벤트 루프 스레드에서 동기로 돌리면 안 된다. 네트워크 하트비트까지 같은 풀을 쓰면, 무거운 작업 한 번이 연결 자체를 끊어먹는다.

공유 스레드 풀은 가장 무거운 태스크 기준으로 격리·사이징을 설계해야 한다. 평균이 아니라 최악값이 기준이다.

마치며

해당 부분은 실제 필요한 동작은 모두 정상이고 간헐적으로만 발생했기 때문에 개발자 테스트나 QA에서 발견되지 못 하였다. 다만, 네이티브 클라이언트 제품군인만큼 사용자가 어떤 환경에서 어떻게 사용할지는 제어할 수 없는만큼, 여러 상황에 대해 대처해야 하고 사용자 경험도 중시해야 한다. 이를 알고는 있었으나 다시 한 번 느끼게 되는 케이스였다.

728x90