← 목록으로

Claude.ai를 2주 만에 3배 빠르게 만든 방법

요약
  • Claude.ai는 2주 동안 핵심 이용 구간을 최적화하여 로딩 시간을 3.1초에서 0.55초로 단축하고 성능을 약 3배 빠르게 개선했습니다.
  • 이 과정에서 Claude가 병목 탐색, 벤치마크 작성, 코드 수정 및 배포 과정을 주도하고 사람이 승인하는 방식으로 진행되었습니다.
  • 웹과 데스크톱 앱에서 실행, 대화 시작, 기존 대화 로딩, 메시지 전송 등 사용자 활동의 95%를 차지하는 흐름을 개선해 핵심 이용 구간을 약 3배 빠르게 만듦
  • 웹페이지를 열어 입력할 수 있을 때까지의 시간은 75백분위 기준 3.1초에서 0.55초로 줄었고, 긴 응답을 표시할 때 CPU 사용량은 약 3분의 1로 감소함
  • Claude가 병목 탐색부터 벤치마크 작성, 코드 수정, 배포 후 확인까지 맡고, 하나의 Slack 채널에서 150개 넘는 작업 스레드를 병렬로 운영함
  • 실제 사용자 지연 시간과 연결되는 지표만 최적화 대상으로 삼고, 개선된 수치를 CI의 새 기준으로 고정해 이후 변경으로 다시 느려지는 것을 막음
  • 사람은 목표와 사용자 경험, 유지보수 비용을 판단하고 모든 변경을 승인했으며, 테스트와 단계적 배포를 바탕으로 3,000건 넘는 변경을 고객 영향 사고나 롤백 없이 반영함
개선 범위와 초기 목표
  • 8월의 2주 스프린트에서 claude.ai와 데스크톱 앱의 핵심 경험을 약 3배 빠르게 개선함
    • 75백분위 기준, claude.ai를 새로 열어 입력할 수 있을 때까지의 시간이 3.1초에서 0.55초로 줄어듦
    • 새 Claude Code 세션 시작은 0.8초에서 0.3초, Claude Cowork 클라우드 세션 로딩은 2.6초에서 0.73초로 단축됨
    • 전체적으로 사용자들의 대기 시간을 매일 수만 시간 줄인 것으로 추산함
  • Claude Tag 베타에서 Opus 5.5와 대략 비슷한 내부 연구 모델을 사용함
    • Claude가 병목을 찾고 벤치마크와 개선안을 구현하며 배포를 감시함
    • 사람은 목표 설정, 절충안 판단, 모든 변경의 승인을 담당함
  • Slack 채널에 상시 지침을 설정해 성능 퇴행 감시와 계측 정확성 및 범위 점검, 대시보드 관리, 개선 구현과 제안, 팀 소통을 맡김
    • 가능한 한 자율적으로 운영하는 것이 궁극적 목표였지만, 당시에는 아직 불가능하다는 전제를 명시함
  • Datadog MCP 서버로 사용 데이터를 분석해 앱 실행, 대화 시작, 기존 대화 로딩, 메시지 전송을 최우선 흐름으로 선정함
    • 네 가지 흐름이 사용자 활동의 95%를 차지하며, 웹과 데스크톱 및 제품별 차이를 반영하면 13개 측정 항목이 됨
    • 사용자 상호작용부터 결과 렌더링까지 측정하고 클라이언트와 서버 작업을 구분하도록 계측을 추가해 기준선을 맞춤
  • 약 20개 프로젝트를 미리 선정하고 Claude가 추산한 밀리초 단위 개선 효과를 합산해 목표를 정함
    • 대부분을 2주 안에 달성할 수 있을 것으로 예상했으나, 3일째에 13개 중 12개 목표를 달성함
초기에 적용한 실행과 탐색 최적화
  • 초기 실행에서는 React 초기화 중에도 입력할 수 있도록 정적 입력창을 HTML에 넣고, 데스크톱 셸의 메인 프로세스가 매번 다시 컴파일하지 않도록 V8 코드 캐시를 사전 컴파일함
  • 대화 간 이동에서는 입력창을 마운트한 상태로 유지하고, 사용자가 세션에 마우스를 올리면 미리 가져오도록 변경함
    • 사이드바 재렌더링은 90% 감소함
  • Claude가 별도로 발견한 기회도 새로운 프로젝트로 진행하면서 초기 목표를 크게 초과함
    • 목표를 다시 설정하고, 아직 탐색하지 않은 영역과 반복 개선할 수 있는 측정 대상을 찾도록 요청함
실제 지연 시간과 연결되는 벤치마크
  • 배포 후 현장 데이터를 기다리지 않고 Claude가 밤새 비동기로 시제품을 검증할 수 있도록 실험실 측정 체계를 구축함
  • 순수 JavaScript의 빈번한 실행 경로에는 Valgrind 명령어 수와 node --predictable을 검토함
    • Chromium에는 같은 방식의 명령어 계수가 없어 V8의 정밀 커버리지 기반 함수 호출 수, React 커밋 수, 레이아웃과 스타일 재계산 수, DOM 변경 수를 대안으로 탐색함
    • 요청 11분 뒤 명령어 수, V8 호출 수, React 커밋, 스타일 재계산, DOM 변경을 각각 다루는 다섯 스레드가 실행 중이었음
  • 벤치마크는 실험실 최적화 지표인 동시에 CI 성능 상한이어야 했음
    • 실제 경과 시간은 사용자가 체감하는 값이지만 잡음이 커서 밀리초 단위 CI 통과 기준으로 쓰기 어려웠음
    • 측정값이 불안정하거나 사용자 지연 시간과 연관성이 입증되지 않는 벤치마크는 폐기함
  • 대화 메시지 트리 구성 루틴과 Claude Code 출력의 상태 줄 스캐너를 대상으로 명령어 수와 실행 시간의 연관성을 검증함
    • 첫 번째 경로에서는 같은 메시지 ID를 세 번 조회하는 메가모픽 딕셔너리 조회가 명령어의 4분의 1을 차지함
    • 한 시간 뒤 두 경로의 명령어 수는 각각 48%, 31%, 실제 실행 시간은 78%, 44% 감소함
    • 이후 해당 경로의 명령어 수를 늘리는 PR은 CI에서 실패하고, 일일 작업은 더 낮은 수치가 나오면 상한도 낮추도록 구성함
  • 핵심은 측정하면 최적화에 착수할 수 있다는 것이었음
    • Claude는 넘어야 할 수치가 생기자마자 개선을 시작할 수 있었으므로, 사람이 가장 큰 효과를 낼 수 있는 일은 측정 대상을 더 찾는 것이었음
스레드마다 반복한 측정과 개선 루프
  • 모든 작업은 하나의 Slack 채널에서 다음 반복 루프로 진행함
    1. 느린 이용 구간을 스크린샷이나 녹화와 함께 스레드로 공유함
    2. Claude가 흐름을 추적하고 문제를 재현하는 벤치마크를 찾거나 만듦
    3. 실험실에서 개선을 확인하면 위험도와 리뷰 범위에 맞게 PR을 나누고, 사용자에게 보이는 변경은 기능 플래그 뒤에 둠
    4. 배포 후 Claude가 배포 상태와 현장 데이터를 확인함
    5. 성능이 좋아지면 벤치마크 상한을 낮춰 고정하고, 그렇지 않으면 플래그를 끄고 다시 개선함
    6. 같은 이용 흐름에서 다음 병목을 찾음
  • 사이드바에서는 Chat과 Cowork 행이 서로 다른 시점에 나타나 페이지 로딩 후 화면이 흔들리는 문제가 있었지만 기존 모니터는 감지하지 못함
    • Cumulative Layout Shift의 개별 이동 점수는 약 0.008로, 양호 기준인 0.1보다 훨씬 낮았음
  • Layout Instability API를 직접 사용해 layout-shift 항목의 sources를 사이드바나 대화 본문 같은 영역과 첫 페인트 전이나 입력 가능 이후 같은 단계에 연결함
    • 통합 테스트는 데이터가 있는 사이드바를 열되 데이터를 첫 페인트 이후까지 지연시키고, 지정한 영역에서 이동이 발생하면 실패하도록 구성함
    • 기존 main에서는 20회 모두 실패, 수정 PR에서는 20회 모두 통과함
  • 현장 계측 결과, 웹 페이지 로드의 31% 에서 사용 가능한 상태가 된 뒤 사용자 조작 없이 화면 요소가 움직였음
    • 늦게 나타나는 헤더 행, 사용자 이름이 로드되며 옆으로 움직이는 입력 커서, 스크롤바가 생기며 이동하는 목록부터 묶어서 수정함
    • 큰 원인을 없앤 뒤 다음 원인을 찾아 같은 과정을 반복함
  • 이런 스레드를 스프린트 동안 150개 넘게 동시에 운영함
병렬 확장으로 발견한 병목
  • 최초 요청을 해결한 뒤에도 스레드를 닫지 않고 개선을 이어가면서, 스레드 하나에서 50~100개 최적화 PR이 나오기도 함
    • 다른 조사나 야간 작업에서 찾은 기회를 바탕으로 Claude가 직접 새 스레드를 여는 경우도 늘어남
  • 새로운 측정마다 추가 병목이 드러남
    • 입력 경로의 React 훅 조사에서는 키 입력마다 재렌더링되는 훅 6,900개와 스토어 구독 900개를 발견함
    • 스타일 재계산 측정에서는 단일 :root:has() 선택자가 DOM 변경마다 24밀리초를 더하고 있었음
    • 첫 페인트 이후 추적에서는 기존 로드 지표가 포착하지 못한 location.reload()가 하루 50만 회의 숨은 재로드를 일으키고 있었음
    • 유휴 탭 프로파일에서는 같은 캐시 스냅샷을 분당 두 번 IndexedDB에 복제하는 작업이 모두 메인 스레드에서 실행되고 있었음
  • CPU 지연을 조사하다가 완료된 코드 블록의 구문 강조가 페이지를 약 1초 멈추는 문제를 발견함
    • 응답 Markdown에 긴 대시나 곡선형 따옴표처럼 Latin-1 밖의 문자가 하나라도 있으면 V8이 전체 문자열을 UTF-16으로 저장해 구문 강조 정규식이 느린 2바이트 경로를 사용함
    • 강조 처리 전에 각 코드 블록을 1바이트 문자열로 복사하는 20줄 변경으로 해결함
  • 가장 바쁜 날에는 하루 200건 넘는 변경을 반영했고, PR의 약 3분의 1에 새 계측이나 안전장치가 들어감
    • 새 계측은 추가 기회를 찾는 스레드로 이어져 2주 차에는 일일 업데이트로 결과를 요약하기도 어려웠음
  • 채널을 공개적으로 운영하면서 다른 팀도 변경 사항의 성능 검토를 요청하기 시작함
    • 새 안전장치와 Claude 스킬의 영향으로 신규 프로젝트도 이전보다 성능을 고려해 작성됨
빠른 변경을 지탱한 안전장치
  • 첫 페인트, 입력창, 대화 본문처럼 핵심 경로를 수정했으므로 안전장치를 먼저 확립함
    • 모든 PR은 자동 코드 리뷰와 최소 한 명의 사람 승인을 거침
    • 최적화 전에 단위 테스트를 작성하고, 사용자에게 보이는 문제를 일으킬 수 있는 변경은 단기 기능 플래그로 보호함
  • 별도 스레드에서 기능 플래그를 긴급 차단용과 점진적 확대용으로 분류하고, 안전해지는 즉시 제거함
    • 2주 동안 약 200개 플래그를 추가했으며, 종료 시점에는 절반 이상을 정리함
  • 빠르게 변경되는 코드베이스에서 성능 개선이 사라지지 않도록 개선 유지 장치에도 투자함
    • 정적 입력창은 HTML 사본을 거의 즉시 표시한 뒤 React가 그 위에 렌더링하므로, 두 화면이 조금만 어긋나도 매끄러운 전환이 깨짐
    • jsdom에서 실제 React 컴포넌트를 렌더링해 정적 마크업을 생성하고, 둘의 불일치를 테스트로 막음
    • 14개 뷰포트 크기에서 정적 화면과 React 화면의 정렬 오차가 1픽셀 이내인지 검사함
    • 전환 중 계속 입력하는 테스트로 키 입력 누락과 순서 변경을 잡음
    • 실제 환경에서는 모든 전환의 이동을 0.1픽셀 단위로 보고하고, 이동이 0이 아닌 이벤트마다 Claude가 스레드를 엶
  • 고위험 변경은 직원, 사용자 1%, 전체 사용자 순서로 배포함
    • 실험실에서 잡지 못한 문제를 내부 사용 단계에서 발견할 수 있도록 함
Chrome 사전 렌더링에서 드러난 예외
  • 정적 입력창을 내부에 배포한 지 4시간 뒤, 새 탭에서 claude.ai를 열면 입력창이 아래로 이동한다는 제보가 들어옴
    • 당일 해당 직원의 49회 로드에서 정적 입력창과 React 사이의 전환 이동은 모두 0픽셀이었으며, 원인은 앱 전환이 아니라 Chrome의 페이지 크기 변경이었음
  • 조직 관리형 Chrome의 새 탭에는 56픽셀 높이의 하단 영역이 있었고, 추측적 로딩으로 페이지를 그 높이에 맞춰 미리 렌더링함
    • 사용자가 주소를 입력하는 동안 숨은 페이지를 렌더링한 뒤, 이동이 완료되면 첫 페인트 약 100밀리초 후 하단 영역이 사라지며 페이지 높이가 늘어남
    • /new는 인사말과 입력창을 페이지 상단에서 높이의 18% 지점에 배치하므로, 56픽셀 증가가 약 10픽셀의 하향 이동으로 나타남
    • 새로고침에는 제거할 새 탭 하단 영역이 없고, 새 탭에서도 사전 렌더링과 첫 페인트 타이밍에 따라 간헐적으로 발생함
  • 기존에는 페이지가 이만큼 일찍 그려지지 않아 드러나지 않았던 문제였음
    • 헤드리스 Chrome에는 접히는 브라우저 UI가 없고 정적 화면과 앱이 함께 움직여 기존 테스트로 포착하지 못함
    • 크기 변경 중 레이아웃을 고정하고 사전 렌더링 흐름을 모사하는 테스트를 추가함
사람이 맡은 야심, 안목, 방향 설정
  • 야심: Claude는 기본적으로 범위를 조심스럽게 잡고, 발견 사항을 티켓으로 남기며, 가능성을 보수적으로 판단하고 일정을 넉넉히 추정함
    • 안전장치를 신뢰한 팀은 더 과감하게 움직이도록 독려함
    • Claude Code에 타이밍 계측을 추가하는 작업을 주중에 하겠다고 했을 때 즉시 배포를 지원하겠다고 하자, Claude는 한 시간 안에 PR을 올리기로 함
    • 목표 달성 후 느슨해진 스레드에는 목표가 종료 지점이 아니라며 추가 개선을 요청함
  • 안목: 모든 스레드에 담당자를 지정하고, 사용자가 감지할 수 있는 변경은 전후 스크린샷이나 녹화로 판단함
    • 표를 셀 단위로 채울지 행 완성 후 표시할지, 로딩 스켈레톤을 즉시 보일지 0.5초 뒤 보일지 결정함
    • 스트리밍 텍스트의 단어별 페이드 효과가 프레임 예산의 5분의 1을 쓸 가치가 있는지도 사람이 따짐
  • 방향: 각 스레드는 벤치마크 하나 또는 이용 흐름 하나에 집중하도록 제한함
    • 우선순위와 사용자 영향, 충돌하는 스레드의 통합, 추가 개선 효과가 줄어든 스레드의 종료를 사람이 결정함
    • 메시지 전송당 2밀리초를 줄이려는 900줄 PR은 빌드 플러그인 유지보수 복잡성에 비해 이득이 작아 거절함
8.33밀리초 프레임 예산과 120fps
  • 실시간 구문 강조 정규식의 최적화를 보여주는 녹화에 Claude가 프레임률 표시를 붙이면서, 스크롤과 스트리밍을 120fps로 개선하는 별도 작업이 시작됨
  • 기본 60Hz인 헤드리스 Chromium 대신 DevTools begin-frame 제어로 결정론적인 120Hz 프레임 진행을 구현함
    • 8.33밀리초 간격의 begin-frame 240회에 정확히 240프레임이 나오는 것을 확인함
    • 각 프레임이 8.33밀리초 예산 안에 들어오는지 직접 검사할 수 있게 됨
  • 긴 응답을 프레임별로 측정하며 느린 부분을 제거함
    • 완료된 블록을 메모이제이션해 청크마다 수행하던 O(message length) 작업을 없앰
    • 늘어나는 코드 펜스의 토큰화 로직을 워커로 옮기고, 표는 셀 단위로 표시함
  • 해당 스레드에서 약 60개 PR을 반영함
    • 긴 응답의 총 메인 스레드 차단 시간이 약 750밀리초에서 200밀리초로 줄어듦
    • CPU 사용량은 약 3분의 1이 됐고, 120Hz MacBook에서 처음부터 끝까지 120fps를 유지함
    • 120Hz 테스트 환경을 야간 작업으로 전환해 Claude가 성능 퇴행을 감시하도록 함
  • 스트리밍 중 프레임 사이 시간을 최적화할 계획은 처음에 없었지만, 측정 가능해지자 반복 개선할 수 있었음
남은 개선 영역
  • claude.ai와 데스크톱 앱은 8월 초보다 약 3배 빨라졌으며, 낮아진 성능 상한을 고정하는 장치로 개선을 유지할 수 있을 것으로 기대함
  • 95백분위 지연 시간, 다른 이용 흐름, 매우 긴 대화에는 여전히 개선 여지가 있음
  • 스프린트 중 Electron, Chromium, Node.js 등 상위 프로젝트에도 기여를 반영했으며, 별도 글에서 다룰 예정임
  • 같은 Slack 채널에서 스레드 단위의 개선을 계속하고 있으며, 이 작업 방식을 규모에 관계없이 이어갈 계획임
그냥 목록으로
원문 보기 ↗