성능 개선 스프린트의 내부 과정을 소개합니다. Claude가 직접 만든 벤치마크, Slack 스레드마다 반복한 개선 루프, 그리고 3,000건에 달하는 변경 사항을 안전하게 배포할 수 있었던 안전장치까지 담았습니다.
올해 8월, 우리는 2주간의 스프린트로 claude.ai와 Claude 데스크톱 앱의 핵심 사용자 경험을 약 3배 빠르게 만들었습니다. 사용자들이 계속 느리다고 말해 왔고, 그 지적은 옳았습니다. 작업은 모두 하나의 Slack 채널에서 진행했으며, 모든 스레드에 Claude가 함께했습니다.
우리는 전체 사용자 활동의 95%를 차지하는 네 가지 사용자 여정에 집중했습니다. 75번째 백분위수 기준으로, claude.ai를 처음 불러올 때 입력할 수 있는 페이지가 뜨기까지 걸리는 시간은 3.1초에서 0.55초로 줄었습니다. 새 Claude Code 세션을 시작하는 시간은 0.8초에서 0.3초로, Claude Cowork 클라우드 세션을 불러오는 시간은 2.6초에서 0.73초로 단축됐습니다. 종합하면 사용자가 매일 기다리는 시간을 수만 시간 아낀 것으로 추산합니다.

이번 작업에는 Opus 5.5와 대략 비슷한 수준의 내부 연구용 모델을 탑재한 Claude Tag(베타)를 사용했습니다. Claude는 병목을 찾고, 벤치마크를 만들고, 개선 사항을 배포하고, 모든 배포를 지켜봤습니다. 우리는 목표를 정하고, 트레이드오프를 판단하고, 모든 변경을 승인하는 방식으로 방향을 잡았습니다. 이렇게 해서 3,000건이 넘는 변경 사항을 병합했지만, 고객에게 영향을 준 장애나 롤백은 단 한 번도 없었습니다. 이 글에서는 무엇을 배포했는지, 어떻게 측정했는지, 그리고 이를 안전하게 해내기 위해 Claude와 함께 만든 루프를 소개합니다.
스프린트를 시작하기 전에, 우리는 다음과 같은 상시 지침을 담아 Slack 채널을 만들었습니다.
@Claude 당신의 역할은 claude.ai 웹사이트와 데스크톱 앱의 성능과 관련된 모든 일을 총괄하는 것입니다. 배포 후 성능 회귀 모니터링, 기존 텔레메트리의 정확성과 포괄성 평가, 잘 정리된 관측 가능성 대시보드 관리, 발견된 문제와 손쉽게 개선할 수 있는 부분에 대한 선제적 해결, 성능 프로젝트 후보 제안, 그리고 사람 팀원과의 소통이 여기에 포함됩니다. […]
이 채널의 궁극적인 목표는 당신이 최대한 자율적으로 일하게 되는 것이지만, 아직은 그것이 가능하지 않다는 것을 알고 있습니다.
우리는 Claude에게 Datadog MCP 서버로 사용량 데이터를 분석해 달라고 요청했습니다. Claude는 영향이 가장 큰 사용자 여정 네 가지를 짚어냈습니다. 앱 실행, 새 대화 시작, 기존 대화 불러오기, 메시지 전송입니다. 웹과 데스크톱, 그리고 여러 제품에 걸쳐 보면 이 여정은 총 13개의 개별 측정 항목이 되었습니다. 기준선을 세우기 위해 서로 직접 비교할 수 있을 때까지 계측 코드를 추가했습니다. 모든 측정은 사용자 상호작용에서 시작해 결과가 화면에 렌더링되는 시점에 끝나며, 클라이언트 작업과 서버 작업을 구분하도록 했습니다.
스프린트는 특정 여정을 겨냥해 직접 고른 약 20개의 프로젝트 목록으로 시작했습니다. Claude가 프로젝트마다 예상 효과를 밀리초 단위로 추정했고, 이 추정치를 합산해 스프린트 목표를 정했습니다. 규모가 상당한 프로젝트도 있었지만, 대부분 2주 안에 해낼 수 있을 것으로 보았습니다.
13개 목표 중 12개를 사흘째에 달성했습니다.
계획했던 프로젝트가 예상보다 일찍 마무리된 것입니다. 앱 실행을 빠르게 하기 위해 정적 컴포저를 HTML에 심어 React가 초기화되는 동안에도 입력할 수 있게 했고, V8 코드 캐시를 미리 컴파일해 데스크톱 셸의 메인 프로세스가 매번 처음부터 다시 컴파일하지 않도록 했습니다. 화면 전환을 빠르게 하기 위해서는 대화 사이를 오가도 컴포저가 마운트된 상태를 유지하고, 사용자가 세션 위에 마우스를 올리면 미리 가져오도록 했으며, 사이드바의 리렌더링을 90% 줄였습니다.
한편 Claude가 스스로 기회를 찾아 새로운 작업 흐름을 제안할 여지도 남겨 두었습니다. 이렇게 시작된 작업들은 금세 독립된 프로젝트로 커졌고, 처음 세운 목표를 크게 뛰어넘었습니다. 그래서 새 목표를 세우고 측정할 대상을 더 찾아 나섰습니다.
@Claude 처음 프로젝트 목록에 있던 거의 모든 항목에 투자했고 그 이상도 했어요. 이쯤에서 한번 새로 정리해 봅시다 […] 아직 탐색하지 않은 곳은 어디인지, 힐 클라이밍으로 개선할 수 있는 건 무엇인지, 지금 가장 기회가 큰 곳은 어디인지요. […] 엉뚱한 아이디어도 환영입니다
처음부터 우리는 배포 주기보다 빠르게 반복하고 싶었습니다. Claude는 몇 시간씩, 심지어 밤새 비동기로 작업할 수 있으니, 실사용 데이터를 기다리지 않고도 프로토타입을 검증하게 하고 싶었습니다. 그러려면 실험 환경에서 성능을 측정할 다른 방법이 필요했습니다.
첫 단서는 Sam이 찾았습니다.

11분 뒤에는 명령어 수, V8 호출 횟수, React 커밋, 스타일 재계산, DOM 변경이라는 서로 다른 측정 항목을 각각 맡은 스레드 다섯 개가 돌아가고 있었습니다.
새 벤치마크는 하나하나 의심하며 살펴봤습니다. 벤치마크에는 두 가지 역할이 있었습니다. 하나는 Claude가 실험 환경에서 개선할 수 있는 지표가 되는 것, 다른 하나는 수치가 내려가기만 하는 CI 안전장치가 되는 것입니다. 결과가 불안정하거나 실제 사용자 지연 시간과 상관관계가 없는 벤치마크는 Claude가 엉뚱한 언덕을 오르지 않도록 과감히 버렸습니다.
@Claude 이 벤치마크들 각각에 대해 힐 클라이밍을 하면 실제 벽시계 시간(wall-clock time) 기준으로 측정 가능한 성능 향상이 나온다는 걸 증명해 주세요. 증명하지 못하는 후보는 벤치를 폐기하겠습니다
사용자가 체감하는 것은 벽시계 시간이지만, 이 값은 변동이 커서 밀리초 단위로는 CI 게이트로 쓰기에 너무 불안정합니다. 명령어 수는 결과가 결정적이라는 점에서 매력적이었지만, 이것이 벽시계 시간과 함께 움직인다는 사실은 여전히 Claude가 증명해야 했습니다.
그래서 Claude에게 두 개의 핫 패스에서 명령어 수를 줄여 보라고 했습니다. 하나는 대화의 메시지 트리를 조립하는 루틴이고, 다른 하나는 Claude Code 출력에서 상태 표시줄을 찾는 스캐너입니다. Claude가 Valgrind로 두 경로를 프로파일링해 보니, 첫 번째 경로 명령어의 4분의 1이 메가모픽 딕셔너리 조회였고, 같은 메시지 ID를 세 번이나 따로 찾고 있었습니다.
한 시간 뒤 Claude는 두 경로의 명령어를 각각 48%, 31% 줄였고, 벽시계 시간도 78%, 44% 감소했습니다. 우리는 새 래칫 두 개를 체크인했습니다. 그때부터 이 경로의 명령어 수를 늘리는 PR은 CI에서 실패했고, 매일 도는 작업이 수치가 내려갈 때마다 상한선을 낮췄습니다.

이 경험은 이번 스프린트의 핵심 교훈으로 이어졌습니다. Claude와 함께라면 측정할 수 있는 것은 무엇이든 해결할 수 있습니다.
예전에 측정은 0단계였습니다. 지표를 추가하고, 데이터가 쌓이길 기다리고, 그제야 문제를 이해하기 시작했죠. Claude와 일할 때는 측정이 곧 개선의 첫걸음입니다. 넘어야 할 숫자가 생기는 순간 Claude는 바로 최적화를 시작할 수 있습니다. 그러니 우리가 할 수 있는 가장 효과적인 일은 측정할 대상을 더 많이 찾는 것이었습니다.
이 모든 작업은 같은 Slack 채널에서, 여러 엔지니어와 Claude가 스레드마다 함께 머리를 맞대며 진행됐습니다. 스프린트는 점차 다음과 같은 루프로 자리 잡았습니다.

예를 들어 보겠습니다. 한 팀원이 페이지가 로드된 뒤에 사이드바 항목이 툭툭 튀어나오는 화면 녹화를 공유했습니다. 채팅 항목과 Cowork 항목이 서로 다른 시점에 나타나서 페이지가 버벅거리는 느낌을 줬습니다. 기존 모니터링으로는 이 문제를 전혀 잡지 못했습니다. 그나마 가까운 지표가 누적 레이아웃 이동(Cumulative Layout Shift)이었지만, 이동 한 번당 점수가 0.008 정도라 '좋음' 기준인 0.1에 한참 못 미쳤습니다.
Issac은 기반이 되는 Layout Instability API를 직접 활용하자는 아이디어를 냈습니다. Claude는 각 layout-shift 항목의 sources을 이름 붙인 영역(예: 사이드바, 대화 내용)과 단계(예: 첫 페인트 이전, 입력 가능 이후)에 매핑하는 텔레메트리 이벤트를 만들었습니다. 또 사이드바에 데이터가 채워진 상태로 페이지를 열되 첫 페인트 이후까지 사이드바 데이터를 붙잡아 두고, 이름 붙인 어떤 영역에서든 이동이 발생하면 실패하는 통합 테스트를 추가했습니다. Claude는 이 테스트를 수정 효과를 증명하는 벤치마크로 활용했습니다. main 브랜치에서는 20번 중 20번 실패했고, 수정 PR에서는 20번 중 20번 통과했습니다.
이벤트를 배포한 뒤 Claude가 실사용 데이터를 살펴보니, 웹 페이지 로드의 31%에서 페이지를 쓸 수 있게 된 이후 사용자 상호작용 없이 무언가가 움직이고 있었습니다. Claude는 원인을 하나씩 짚어 나갔습니다. 늦게 도착하는 헤더 행, 사용자 이름이 로드되자 옆으로 밀려난 캐럿, 스크롤바가 나타나면서 움직인 목록 같은 것들이었습니다. Claude는 가장 큰 원인들을 한 번에 고쳤고, 그것들이 사라지자 다음 묶음을 찾아냈습니다.

이것은 스레드 하나의 이야기였습니다. 스프린트 동안 우리는 동시에 150개가 넘는 스레드를 돌렸습니다.
루프가 스레드 하나에서 잘 돌아가자, 더 많은 스레드에서 돌리는 건 스레드를 더 여는 일에 불과했습니다. Claude는 처음 요청이 해결되었다고 스레드를 닫는 대신 계속 이어 갔습니다. 스레드 하나에서 최적화 PR이 쉰 개, 많게는 백 개까지 올라오기도 했습니다. 별도 조사나 야간 작업 중에 스스로 발견한 기회를 쫓기 위해 우리가 아닌 Claude가 직접 새 스레드를 여는 일도 점점 늘었습니다. 채널에 있던 엔지니어 Shelley는 "[이 모델은] 숫자에 미친 존재예요"라고 말했습니다.
측정할 때마다 개선할 것이 나왔습니다. Claude가 React 훅 전수 조사를 해 보니 컴포저의 입력 경로에 훅 6,900개와 스토어 구독 900개가 있었고, 키를 누를 때마다 리렌더링이 일어나고 있었습니다. 스타일 재계산을 세어 보니 :root:has() 셀렉터 하나가 DOM이 바뀔 때마다 24밀리초를 더하고 있었습니다. 첫 페인트 이후의 코드 경로를 추적하자, 남아 있던 location.reload() 때문에 하루 50만 번의 보이지 않는 리로드가 발생하고 있었는데, 기존 로드 지표로는 전혀 보이지 않는 문제였습니다. 유휴 상태 탭의 프로파일러 샘플을 읽었을 때는 똑같은 캐시 스냅샷이 1분에 두 번씩 IndexedDB로 복제되고 있었고, 그 작업이 전부 메인 스레드에서 돌고 있었습니다.
스레드가 어디로 이어질지는 대개 알 수 없었습니다. CPU 끊김을 훑어보던 Claude는 다 작성된 코드 블록에 하이라이팅을 적용할 때 페이지가 1초가량 멈출 수 있다는 것을 발견했습니다. 실험 환경에서 파고들어 보니 범인은 엠 대시였습니다. 답변의 마크다운에 엠 대시나 둥근 따옴표처럼 Latin-1 범위 밖의 문자가 하나라도 있으면 V8은 문자열 전체를 UTF-16으로 저장하는데, 그러면 구문 강조용 정규식이 모두 더 느린 2바이트 경로를 타게 됩니다. Claude는 하이라이팅 전에 각 코드 블록을 1바이트 문자열로 복사하는 20줄짜리 변경으로 이 문제를 해결했습니다.

둘째 주에는 결과물이 너무 많아 일일 업데이트로 요약하기조차 버거웠습니다. 가장 바쁜 날에는 200건이 넘는 변경이 반영됐습니다. Claude는 계속해서 새 벤치마크를 제안했고, PR의 약 3분의 1에는 추가 텔레메트리나 안전장치가 포함되어 있었으며, 새 계측 도구가 생길 때마다 더 많은 기회를 담은 스레드가 늘어났습니다.
한 채널에서 일하니 모든 것이 공개적으로 이루어졌습니다. 우리는 서로의 스레드를 넘나들며 결정을 토론하고 성과를 함께 축하했습니다. 소문이 퍼지면서 다른 팀들도 자기 변경 사항의 성능 검토를 받으러 채널을 찾아오기 시작했습니다. 그동안 도입된 안전장치와 Claude 스킬 덕분에 새 프로젝트도 은연중에 더 성능 좋은 방식으로 작성되었습니다.
이런 속도는 이미 예상하고 준비해 두었습니다. 우리가 건드리는 거의 모든 곳이 첫 페인트, 컴포저, 대화 내용처럼 핵심 경로였기 때문에, 안전 장치를 처음부터 갖춰 놓았습니다. 모든 PR은 자동 리뷰를 거쳐 최소 한 명의 사람이 승인해야 했고, 단위 테스트는 항상 최적화보다 먼저 작성했으며, 사용자에게 보이는 문제를 일으킬 수 있는 변경은 수명이 짧은 기능 플래그 뒤에서 배포했습니다.
플래그가 쌓이기 시작하자 롤아웃과 정리를 조율하는 스레드를 따로 열었습니다. Claude는 모든 플래그를 킬 스위치와 점진적 확대용(ramp)으로 분류하고, 안전해지는 즉시 하나씩 정리했습니다. 2주 동안 도입한 플래그는 200개에 가까웠고, 그중 절반 넘게가 스프린트가 끝날 때쯤 이미 정리되었습니다.
또한 코드 변경이 빠르게 이어지는 코드베이스에서는 성능 개선 효과가 쉽게 퇴색하며, Anthropic에서는 코드가 빠르게 배포된다는 점도 알고 있었습니다. 그래서 어떤 프로젝트가 효과를 입증하면, 그 성과를 지킬 방법에도 투자했습니다. 정적 컴포저가 대표적인 예로, 이 기능은 구조상 깨지기 쉽습니다. 사용자에게 페이지의 HTML 사본을 거의 즉시 보여 주고, React가 그 위에 그대로 덧그리도록 하기 때문입니다.

React 렌더링 결과가 단 1픽셀만 어긋나도 이 마법은 깨집니다. 그래서 Claude는 수십 개의 안전장치를 만들었습니다.
모든 문제를 실험 환경에서 잡을 수는 없기 때문에, 가장 오래된 안전장치인 점진적 롤아웃도 활용했습니다. 위험도가 높은 변경은 먼저 직원에게, 그다음 사용자의 1%에게, 마지막으로 전체에 배포했습니다. 정적 컴포저를 사내에 배포하고 네 시간 뒤, 한 팀원이 우리 지표로는 전혀 보이지 않던 레이아웃 이동을 담은 화면 녹화를 공유했습니다. 새 탭에서 claude.ai를 열면 컴포저가 아래로 툭 떨어졌는데, 우리 코드 때문이 아니었습니다.

Claude는 어떻게인지 이 원인을 Chrome의 추측 로딩(speculative loading)에 있는 예외 상황까지 추적해 냈습니다. 사용자가 주소창에 URL을 입력하는 동안 Chrome은 현재 탭의 높이에 맞춰 백그라운드에서 페이지를 미리 렌더링합니다. 그런데 조직에서 관리하는 브라우저는 푸터 때문에 새 탭 페이지가 약간 더 짧습니다. 사용자가 Enter를 누르면 claude.ai의 첫 프레임은 이 약간 짧은 레이아웃으로 그려지고, Chrome이 약 0.1초 뒤에 크기를 조정합니다. Claude는 크기가 조정되어도 레이아웃이 고정되도록 처리했고, 우리는 이 미리 렌더링 흐름을 재현하는 테스트를 추가했습니다.
이 루프는 생산적이었지만 자율적이지는 않았습니다. 루프를 빠르고 안전하게, 그리고 올바른 방향으로 유지하는 일은 우리 몫이었고, 여기에는 세 가지 역할이 있었습니다.
야심. Claude는 기본적으로 작업 범위에 신중합니다. 발견한 문제는 티켓으로 남기고, 실현 가능성에는 단서를 달고, 추정치에는 여유를 얹습니다. 하지만 우리는 안전장치를 믿고 있었습니다. 특히 초반에 우리가 한 일의 상당 부분은 Claude가 더 대담해지도록 북돋는 것이었습니다.

세워 둔 목표를 달성하기 시작하자 스레드의 속도가 느려지는 것이 눈에 띄었습니다. 그래서 Sam이 스레드를 돌며 같은 메시지를 남겼습니다. "계속 줄여 봅시다. 목표는 종착점이 아닙니다. 다음은 뭐죠? 야심 차게 가 봅시다."
안목. 모든 스레드에는 이름이 명시된 담당자가 있었고, Claude는 사용자가 체감할 수 있는 변경마다 전후 스크린샷이나 녹화 영상을 붙여 담당자가 판단하도록 했습니다. 표는 셀 단위로 채워야 할까요, 아니면 행이 완성될 때까지 기다려야 할까요? 로딩 스켈레톤은 바로 보여 줄까요, 0.5초 뒤에 보여 줄까요? 스트리밍 텍스트에 단어 단위 페이드를 적용하는 것이 프레임 예산의 5분의 1을 쓸 만한 가치가 있을까요? Claude가 밀리초를 깎을 방법을 찾으면, 우리는 트레이드오프를 따져 봤습니다.
방향. 우리는 각 스레드를 하나의 벤치마크나 여정에 집중하도록 일부러 좁게 유지했고, Claude에게도 그 범위 안에서만 개선점을 찾게 했습니다. 스레드를 못을 찾는 150개의 망치라고 생각한 것입니다. 우리가 내린 판단 대부분은 순서와 사용자 영향에 관한 것이었습니다. 어떤 화면을 먼저 다룰지, 서로 부딪히는 스레드를 어떻게 합칠지, 성과가 한계에 다다른 스레드를 언제 닫을지 같은 결정입니다. 900줄짜리 PR 하나에는 한 줄짜리 답변이 달렸습니다. "전송당 2ms를 얻자고 이 빌드 플러그인을 유지하는 복잡성을 감수할 가치는 없으니 여기서 마무리하겠습니다."
곁가지로 진행한 작업 하나가 모든 요소가 맞물리는 모습을 보여 줍니다. 실시간 구문 강조에 쓰이는 정규식의 최적화를 보여 주기 위해, Claude는 실험 환경에서 긴 답변이 스트리밍되는 화면 녹화를 첨부했습니다. 녹화 구석에는 애니메이션 프레임 타임스탬프로 페이지 안에서 계산한 프레임 레이트 표시도 넣어 두었습니다.

측정 방법과 야심이 갖춰지자 Claude는 본격적으로 작업에 들어갔습니다. 화면에 그려지는 프레임 하나당 예산은 8.33밀리초였고, Claude는 긴 답변을 프레임 단위로 하나씩 넘겨 가며 각각의 소요 시간을 재서 느린 부분을 찾았습니다. 완성된 블록을 메모이제이션해서 청크마다 반복되던 O(message length) 작업을 없앴고, 늘어나는 코드 펜스의 토큰화 로직은 워커로 옮겼으며, 표는 셀 단위로 나타나게 했습니다.
이 스레드 하나에서만 PR을 60개 가까이 반영했습니다. 긴 답변이 메인 스레드를 막는 시간은 총 750밀리초 안팎에서 200밀리초 안팎으로 줄었고, CPU 사용량은 약 3분의 1 수준이 되었으며, 120Hz MacBook에서 처음부터 끝까지 120fps를 유지했습니다. 이 120Hz 테스트 환경은 야간 작업으로 자리 잡아, Claude가 회귀 여부를 계속 지켜보고 있습니다.
웹과 데스크톱의 Claude에서 긴 답변이 이제 약 4배 더 부드럽게 스트리밍됩니다.
스트리밍 렌더러를 다시 만들어 아직 변경 중인 부분만 건드리도록 했습니다. 그 결과 성능이 낮은 노트북에서 긴 답변이 멈칫하는 빈도는 9분의 1로 줄었고, 최악의 멈춤은 4.5배 짧아졌으며, 120Hz MacBook에서는 처음부터 끝까지 120fps를 유지합니다.
— ClaudeDevs (@ClaudeDevs) 2026년 8월 24일
스프린트를 시작할 때만 해도 스트리밍 중 프레임 사이의 밀리초를 힐 클라이밍하리라고는 계획하지 않았습니다. 그런데 알고 보니 우리는 그것을 셀 수 있었고, 셀 수 있는 것이라면 Claude가 올라갈 수 있었습니다.
오늘날 claude.ai와 데스크톱 앱은 8월 초보다 약 3배 빠르며, 래칫이 이 속도를 계속 지켜 줄 것입니다. 하지만 아직 끝난 것은 아닙니다. 95번째 백분위수, 다른 여정, 매우 긴 대화에는 여전히 개선할 여지가 있습니다. 별도의 글에서는 스프린트 중 업스트림까지 거슬러 올라가 Electron, Chromium, Node.js 등에 기여한 곁가지 작업들도 소개할 예정입니다.
결과를 사내에 공유했을 때 Issac은 이렇게 말했습니다. "6개월 전만 해도 이게 가능하다고 누가 말했다면 믿지 못했을 거예요." 우리는 앞으로도 이런 방식으로, 스레드 하나씩, 어떤 규모에서든 계속 일할 계획입니다. 채널은 지금도 이어지고 있습니다.
Alfred Xing, Anthony Morris, Benjamin Pasero, Chase McCoy, Joshua N., Luke Deen Taylor, Marius Schulz, Shelley Vohr가 함께했습니다. 더 야심 차게 나아가도록 격려해 준 Boris Cherny에게 특별히 감사드립니다.