조사

Cursor 앱을 안정적으로 유지하기

Andrew Chan & Kevin Nguyen읽는 데 7분

많은 사용자가 하루 종일 Cursor를 사용하기 때문에, 드물게 발생하는 충돌조차도 업무에 큰 지장을 줄 수 있습니다. 동시에 사용자 수가 늘고 하위 에이전트, Instant Grep, browser use 같은 점점 더 복잡한 기능을 배포하면서, 앱을 안정적으로 유지하는 일도 더 어려워졌습니다.

이러한 충돌의 대부분은 앱이 메모리를 모두 소진하면서(OOM) 발생합니다. 지난 몇 달 동안 저희는 충돌과 메모리 압박을 더 잘 파악할 수 있는 시스템을 구축하고, 핵심 경로에 대해 신뢰도 높은 수정과 최적화를 적용했으며, 회귀가 배포 전에 잡아낼 수 있도록 보호 장치도 마련했습니다.

Cursor 앱의 모든 버전을 통틀어 집계한 세션당 OOM 비율은 2월 말 정점 이후 80% 감소했으며, 요청당 OOM 비율은 3월 1일 이후 73% 감소했습니다. 이 글에서는 이러한 성과를 가능하게 한 시스템을 자세히 설명합니다.

시간에 따른 세션당 OOM 비율, 2월 말 이후 80% 감소를 보여줌시간에 따른 세션당 OOM 비율, 2월 말 이후 80% 감소를 보여줌

불안정성 탐지 및 측정

당사의 데스크톱 앱은 오픈 소스인 Visual Studio Code와 Electron을 기반으로 만들어져 있으며, 이로 인해 멀티프로세스 아키텍처를 갖습니다. 즉, 편집기와 새 에이전트 창을 구동하는 렌더러 프로세스나 확장 기능, 스토리지, 에이전트 기능을 구동하는 유틸리티 프로세스 중 어느 쪽에서든 충돌이 발생할 수 있습니다.

렌더러 충돌은 사용자가 편집기를 아예 사용할 수 없게 되므로 가장 심각합니다. 저희가 파악한 바에 따르면 이런 충돌은 대부분 V8 메모리 한도에 도달할 때 발생하며, 최근 저희가 가장 집중하고 있는 부분이기도 합니다. 확장 기능 충돌도 언어 서비스처럼 중요한 기능을 방해할 수 있지만, 일반적으로는 사용자에게 큰 영향 없이 복구됩니다.

모든 치명적 충돌은 영향을 받은 프로세스, 충돌 유형, 기기 및 애플리케이션 메타데이터, 그리고 가능한 경우 미니덤프와 스택 트레이스 같은 컨텍스트와 함께 텔레메트리를 통해 보고됩니다.

이러한 충돌 이벤트를 바탕으로, 저희는 앱 버전별로 세분화할 수 있는 지표를 구축했으며 세션당 또는 요청당 비율을 계산할 수 있습니다. 전자는 대략 얼마나 많은 세션이 충돌을 겪는지를 보여주고, 후자는 영향을 받은 세션에서 충돌 문제가 얼마나 심각한지를 보여줍니다. 이러한 대시보드는 충돌 이벤트가 발생한 지 몇 분 안에 업데이트되므로, 새 버전 출시를 면밀히 추적하고 잠재적인 회귀를 빠르게 탐지할 수 있습니다.

시간 경과에 따른 OOM 충돌, 3월 1일 이후 73% 감소시간 경과에 따른 OOM 충돌, 3월 1일 이후 73% 감소

두 가지 디버깅 전략

앱 충돌과 메모리 부족 문제를 디버깅하기 위해 두 가지 접근 방식을 사용합니다.

하향식

첫 번째는 메모리를 가장 많이 사용하는 기능에 초점을 맞춘 하향식 조사입니다. 어떤 기능이 메모리 집약적이라는 사실이 알려져 있다면, 실험 플랫폼인 Statsig에서 충돌 지표를 해당 기능 플래그와 연결한 뒤 A/B 테스트를 통해 그 기능이 충돌율에 얼마나 기여하는지 측정할 수 있습니다.

또한 충돌와 강한 상관관계가 있으면서 개발 환경에서 더 쉽게 관찰할 수 있는 프록시 지표를 추적할 수도 있습니다. 그중 하나가 지나치게 큰 메시지 payload입니다. 저희 앱은 멀티 프로세스 아키텍처를 사용하므로, 데이터가 프로세스 간 채널과 영속성 계층을 통해 에디터, 확장 기능, 에이전트 사이에서 계속 전달됩니다. 저희는 일정 임계값보다 큰 메시지를 추적할 수 있도록 이 두 경로 모두에 계측을 추가하고, 메모리 문제와 강한 상관관계를 보이는 각 메시지를 애플리케이션 코드의 발생 원인까지 추적할 수 있도록 호출 스택도 첨부합니다.

특정 충돌가 발생한 순간에 어떤 일이 있었는지 재구성하기 위해, 병렬 에이전트 사용, 도구 호출, 터미널 같은 기능에 대해 브레드크럼(오류에 첨부되는 특수한 메타데이터 로그)을 추가해 각 충돌 event에 그 이전 활동 기록이 담기도록 합니다.

상향식

상향식 조사에서는 개별 충돌 이벤트를 근본 원인까지 추적합니다. 첫 단계는 프로세스가 종료된 순간에 무슨 일이 일어났는지 포착하는 것입니다. 저희는 메인 프로세스에서 Chrome DevTools Protocol(CDP)을 사용해 메모리 부족 오류를 탐지하고 크래시 스택을 실시간으로 수집하는 충돌 감시 서비스를 실행하고 있으며, Electron 업스트림도 패치해 무거운 CDP 메커니즘 없이도 이러한 스택을 가져올 수 있게 했습니다. 이렇게 수집된 크래시 스택은 매일 실행되는 자동화로 전달되며, 각 스택을 자세히 분석하고, 수정 효과를 높은 신뢰도로 기대할 수 있는 스택에는 최적화 PR을 만들고, 버전별로 문제가 해결되었는지 검증합니다.

세션 전반에 걸쳐 메모리가 어떻게 누적되는지 이해하기 위해 저희는 힙 스냅샷을 살펴봅니다. Cursor가 너무 많은 메모리를 사용하고 있다고 탐지되면, 사용자에게 힙 스냅샷을 캡처해 보내 달라고 요청합니다. 이러한 스냅샷에는 열려 있는 에디터나 채팅의 내용처럼 민감한 정보가 포함될 수 있으므로, 전송은 전적으로 사용자 선택에 맡겨집니다. 하지만 메모리 압박이 누적되는 과정을 특정 객체와 이를 붙잡고 있는 retainer까지 추적하는 데 매우 큰 도움이 되기 때문에, 사용자가 참여해 주시면 저희는 늘 감사하게 생각합니다.

retainer를 보여주는 Cursor의 힙 스냅샷 도구retainer를 보여주는 Cursor의 힙 스냅샷 도구

전체 사용자층에서 나타나는 메모리 사용량 패턴을 이해하기 위해, 저희는 낮은 샘플링 비율로 연속적인 힙 할당 프로파일링을 실행합니다. 저희는 이 데이터를 앱 버전별로 집계해 호출 스택별 메모리 압박 분석을 만듭니다. 이를 통해 앱 세션 전반의 메모리 압박을 한눈에 파악할 수 있으며, 버전 간 diff까지 수행해 최신 앱 버전의 특정 할당 경로가 이전 버전에 비해 개선되었는지 아니면 악화되었는지, 그리고 그 폭이 얼마나 되는지도 확인할 수 있습니다.

표적화된 완화 조치

이 두 가지 조사 방법을 통해, 충돌은 대체로 두 가지 패턴 중 하나로 나타난다는 점을 확인했습니다.

첫 번째는 급성 OOM으로, 메모리가 갑자기 치솟은 뒤 프로세스가 종료되는 경우입니다. 이런 문제는 보통 크래시 스택에서 발견되며, 힙 덤프나 연속 프로파일에서는 드물게만 나타납니다. 매우 흔한 원인 중 하나는 어떤 기능이 한 번에 너무 많은 데이터를 불러오는 경우인데, 이는 저희 앱이 사용자 워크스페이스의 내용을 폭넓게 다루기 때문에 디스크나 IPC를 통해 파일 전체 내용을 자주 불러오면서 발생할 수 있습니다. 실제로 일부 사용자 워크스페이스에는 앱이 버거워할 정도로 매우 큰 파일이 들어 있는 경우가 있었고, 그래서 킬스위치를 추가하거나 큰 blob 처리를 여러 청크로 나누는 일이 매우 중요했습니다.

두 번째는 서서히 진행되는 OOM으로, 세션이 이어지는 동안 메모리가 조금씩 증가하다가 결국 프로세스가 한계를 넘게 되는 경우입니다. 이런 문제는 수동으로 관리하는 상태가 제대로 정리되지 않거나, 남아 있는 강한 참조 때문에 리소스 누수가 생길 때 발생합니다. 이는 힙 덤프에서 안정적으로 드러나며, retainer를 추적하고 수명이 긴 객체의 생명주기를 정리해 해결할 수 있습니다. 저희는 이미 VSCode에 몇 가지 메모리 누수 수정 사항을 업스트림으로 반영했으며, 앞으로 더 추가할 예정입니다.

확장 기능 충돌 역시 메모리 부족 때문에 발생할 수 있으며, 저희는 이를 부분적으로 프로세스 격리를 통해 완화하고 있습니다. 대략적으로 말하면, 확장 기능을 각각 독립적으로 격리된 프로세스에서 실행해 한 확장 기능의 충돌이나 장시간 실행 작업이 다른 확장 기능의 동작에 영향을 주지 않도록 합니다. 이는 Chrome이 탭들을 서로 격리하는 방식과 비슷하며, 그 대가로 시스템 메모리를 약간 더 사용하게 됩니다.

회귀를 방지하면서 속도 유지하기

앱 충돌을 수정하는 일은 보통 새로운 충돌이 발생하지 않도록 막는 일보다 더 단순합니다. 수정은 대상이 명확하기 때문입니다. 반면 예방은 에이전트로 얻은 속도를 희생하지 않으면서도 모든 개발자가 자신의 변경이 안정성에 미치는 영향을 인식하게 해야 하므로, 개발 과정과 도구 모두에 투자해야 합니다.

저희가 이를 위해 취하고 있는 몇 가지 접근 방식은 다음과 같습니다:

  • 지금까지 겪은 모든 주요 OOM 또는 앱 충돌 유형별 Bugbot rules
  • 에이전트 기반 컴퓨터 사용을 통해 애플리케이션에 손쉽게 스트레스 테스트를 수행할 수 있게 해주는 Skills
  • 누수를 방지하기 위해 수동으로 관리하던 리소스를 가비지 컬렉션으로 대체하는 것처럼, 실수를 유발하는 요소 없애기
  • 모든 코드 변경 후 실행되는 전통적인 자동 성능 테스트
  • 지표 회귀 시 자동 롤백 같은 방법으로 탐지부터 대응까지의 루프 닫기

새로운 세대의 소프트웨어를 위한 안정성

에이전트 기반 소프트웨어 개발은 새로운 기능을 배포하는 일을 그 어느 때보다 쉽게 만들었지만, 동시에 성능 문제와 버그를 더 쉽게 초래하기도 합니다. 그와 동시에 애플리케이션 안정성을 확보하려면 여전히 소프트웨어 엔지니어링의 기본기가 필요하며, 문제를 해결하고 예방하기 위한 에이전트 기반 전략을 통해 이를 새로운 세대에 맞게 발전시켜야 합니다.

고품질 소프트웨어를 만드는 일은 언제나 어려웠고, 지금은 그 어느 때보다 더 중요합니다. 여기에 열정을 가진 분이라면, 여러분의 이야기를 듣고 싶습니다.

분류: 조사

작성자s: Andrew Chan & Kevin Nguyen