← 목록으로
지난 소식
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 코드 캐시를 사전 컴파일함
- 대화 간 이동에서는 입력창을 마운트한 상태로 유지하고, 사용자가 세션에 마우스를 올리면 미리 가져오도록 변경함
- 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 채널에서 다음 반복 루프로 진행함
- 느린 이용 구간을 스크린샷이나 녹화와 함께 스레드로 공유함
- Claude가 흐름을 추적하고 문제를 재현하는 벤치마크를 찾거나 만듦
- 실험실에서 개선을 확인하면 위험도와 리뷰 범위에 맞게 PR을 나누고, 사용자에게 보이는 변경은 기능 플래그 뒤에 둠
- 배포 후 Claude가 배포 상태와 현장 데이터를 확인함
- 성능이 좋아지면 벤치마크 상한을 낮춰 고정하고, 그렇지 않으면 플래그를 끄고 다시 개선함
- 같은 이용 흐름에서 다음 병목을 찾음
- 사이드바에서는 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 채널에서 스레드 단위의 개선을 계속하고 있으며, 이 작업 방식을 규모에 관계없이 이어갈 계획임