Chrome 팀은 단순히 '대부분의 시간 동안' 빠른 것만으로는 충분하지 않으며, '항상' 빨라야 한다고 믿습니다. 오늘 'The Fast and the Curious' 포스트에서는 모든 웹사이트에서 발생하는 사용자 상호작용에 Chrome이 어떻게 반응하는지 필드 데이터를 조사하여 Core Web Vitals(주요 웹 지표)에 기여하고, 궁극적으로 웹 성능을 개선한 과정을 살펴봅니다.
매일 수십억 명의 사람들이 웹을 통해 일을 처리함에 따라, 브라우저는 한 번에 수많은 앱을 호스팅하는 역할을 더 많이 맡게 되었고 리소스 경합(resource contention)은 큰 과제가 되었습니다. 멀티 프로세스 구조인 Chrome 브라우저는 CPU와 메모리는 물론, 내부 서비스(이 글에서는 네트워크 서비스) 간의 작업 대기열을 두고도 여러 리소스와 경쟁합니다.
이것이 바로 우리가 실제 사용자 경험의 권위 있는 소스인 Chrome 사용자 필드 데이터를 통해 느린 상호작용을 식별하고 해결하는 데 집중해 온 이유입니다. 우리는 Chrome Canary에서 익명화된 Perfetto 트레이스를 기록하고, 개인정보 보호 필터를 사용하여 이를 보고함으로써 필드 데이터를 수집합니다.
느린 상호작용에 대한 필드 데이터를 분석하던 중 한 가지 특별한 원인이 눈에 띄었습니다. 바로 네트워크 서비스로부터 현재 사이트의 쿠키를 가져오기 위해 반복적으로 발생하는 동기 호출(synchronous calls)이었습니다.
그 배경을 한 번 살펴보겠습니다.
진화하는 웹 환경에서의 쿠키
쿠키는 웹 플랫폼의 시작부터 함께해 왔습니다. 보통 다음과 같이 생성됩니다:
document.cookie = "user=Alice;color=blue"
그리고 나중에 이렇게 가져옵니다:
// getCookie 헬퍼 메소드가 있다고 가정할 때:
getCookie("user", document.cookie)
단일 프로세스 브라우저에서는 쿠키 저장소(cookie jar)를 메모리에 유지했기 때문에 구현이 간단했습니다.
시간이 흐르며 브라우저는 멀티 프로세스 구조로 변했고, 쿠키 저장소를 호스팅하는 프로세스는 점점 더 많은 쿼리에 응답해야 하는 책임을 갖게 되었습니다. 하지만 웹 사양(Web Spec)에 따라 자바스크립트는 쿠키를 동기적으로 가져와야 하므로, 각 document.cookie 쿼리에 응답하는 작업은 블로킹(blocking) 작업이 됩니다.
작업 자체는 매우 빠르기 때문에 일반적으로는 괜찮았지만, 여러 웹사이트가 네트워크 서비스에 쿠키(및 기타 리소스)를 요청하는 부하가 높은 상황에서는 요청 대기열이 밀릴 수 있습니다.
우리는 느린 상호작용의 필드 트레이스를 통해 일부 웹사이트가 쿠키를 연속으로 여러 번 가져오는 비효율적인 상황을 유발하고 있음을 발견했습니다. 모든 탐색 과정에서 GetCookieString() IPC(프로세스 간 통신)가 얼마나 자주 중복되는지(지난번과 동일한 값이 반환되는지) 측정하기 위해 추가 지표를 도입했습니다. 놀랍게도 쿠키 액세스의 87%가 중복이었으며, 어떤 경우에는 초당 수백 번씩 발생하기도 한다는 사실을 알아냈습니다.
document.cookie의 단순한 디자인이 역효과를 내고 있었습니다. 웹의 자바스크립트는 이를 로컬 값처럼 사용하고 있었지만, 실제로는 원격 조회(remote lookup)였기 때문입니다. 이것이 컴퓨터 과학의 전형적인 사례인 캐싱 문제일까요? 꼭 그렇지만은 않습니다!
웹 사양은 협력 관계인 도메인이 서로의 쿠키를 수정할 수 있도록 허용합니다. 따라서 렌더러 프로세스당 단순한 캐시를 두는 방식은 작동하지 않았습니다. 사이트 간의 쓰기 작업이 전파되는 것을 막아 (예를 들어 전자 상거래 앱에서 장바구니가 동기화되지 않는 등) 오래된 쿠키 문제를 일으킬 수 있기 때문입니다.
새로운 패러다임: 공유 메모리 버저닝(Shared Memory Versioning)
우리는 이를 Shared Memory Versioning이라 부르는 새로운 패러다임으로 해결했습니다. 핵심 아이디어는 이제 document.cookie의 각 값에 단조 증가(monotonically increasing)하는 버전을 쌍으로 부여하는 것입니다. 각 렌더러는 document.cookie를 마지막으로 읽은 값과 해당 버전을 함께 캐싱합니다. 네트워크 서비스는 공유 메모리에 각 document.cookie의 버전을 호스팅합니다. 이를 통해 렌더러는 네트워크 서비스에 프로세스 간 쿼리를 보낼 필요 없이 자신이 최신 버전을 가지고 있는지 여부를 판단할 수 있습니다.
이를 통해 쿠키 관련 프로세스 간 메시지를 80% 줄였고, document.cookie 액세스 속도를 60% 향상시켰습니다 🥳.
가설 검증
알고리즘을 개선하는 것도 좋지만, 우리가 궁극적으로 관심을 두는 것은 그 개선이 사용자에게 느린 상호작용을 실제로 줄여주느냐 하는 것입니다. 즉, 지연된 쿠키 쿼리가 느린 상호작용의 중요한 원인이었다는 가설을 테스트해야 했습니다.
이를 위해 Chrome의 A/B 테스트 프레임워크를 사용하여 효과를 연구했고, 리소스 경합을 줄이기 위한 다른 개선 사항들과 결합하여 모든 플랫폼에서 가장 느린 상호작용을 약 5% 개선했음을 확인했습니다. 결과적으로 더 많은 웹사이트가 Core Web Vitals를 통과하게 되었습니다 🥳. 이 모든 노력이 모여 사용자에게 더 매끄러운 웹 환경을 제공하게 되었습니다.
이 기능이 1%(11월), 50%(12월), 그리고 모든 사용자(2월)에게 배포됨에 따라 Chrome에서 측정된 웹 전반의 가장 느린 상호작용 가중 평균 변화 타임라인.
Chrome 엔지니어링 팀의 Gabriel Charette, Olivier Li Shing Tat-Dupuis, Carlos Caballero Grolimund, François Doray 작성


