3-3. 세션 리플레이 (rrweb) 25.12.23 ~ 25.12.24

배민이 해냈던 그것

배민 기술 블로그에서 가장 인상 깊었던 건 "기록방 URL 하나로 당시 콘솔/네트워크/DOM을 다시 볼 수 있다"는 부분이었다. QA나 기획이 버그를 재현할 때, 개발자한테 스크린샷 찍어서 보내는 대신 URL 하나만 던지면 되는 거다.

카카오 발표에는 없었던 기능인데, 배민이 이걸 해냈다는 게 부러웠다. 그리고 이걸 나도 해보고 싶었다.

rrweb이라는 오픈소스

rrweb은 웹 페이지의 DOM 변경을 기록했다가 나중에 재생하는 라이브러리다. Sentry의 Session Replay도 이걸 기반으로 만들어졌다.

원리는 이렇다. 초기 DOM 스냅샷을 찍고, 이후 모든 DOM 변경(mutation), 마우스 이동, 클릭, 스크롤 같은 이벤트를 시간순으로 기록한다. 재생할 때는 이 이벤트들을 시간 순서대로 다시 적용하면 사용자가 실제로 조작했던 것처럼 재생된다.

2024년부터 업데이트가 거의 없고 2.0.0이 알파로만 나와 있지만, 쓰기엔 문제 없어 보였다.

DevTools에 Replay 패널 추가하기

12월 23일, devtools-frontend 레포에 커스텀 패널을 추가하기 시작했다.

Welcome 패널과 Session Replay 패널을 추가하고, 지원하지 않는 패널은 숨기는 작업을 했다(devtools-frontend PR #2). 그 다음으로 CDP 프로토콜에 Recording 관련 도메인도 추가했다(devtools-frontend PR #3).

문제는 DevTools 프론트엔드와 rrweb 리플레이어를 어떻게 연결하느냐였다. DevTools는 CDP 메시지로 통신하는데, rrweb 리플레이는 CDP 표준에 없는 커스텀 기능이다. 그래서 커스텀 CDP 도메인을 정의하고, Inspector 쪽에서 이걸 처리하는 방식으로 풀었다.

Inspector → 커스텀 CDP: "Recording.startReplay" → 릴레이 서버 → 클라이언트
클라이언트 → rrweb 이벤트 수집 → 릴레이 서버 → Inspector
Inspector → rrweb Player로 재생

DevTools 프론트엔드에 패널을 추가하는 것 자체가 꽤 고생이었다. Chromium 빌드 시스템이 복잡하기로 유명한데, devtools-frontend도 그 생태계의 일부라 수정할 때마다 빌드 시간이 길었다. 그래도 AI한테 "이 구조에서 새 패널을 추가하려면 어떤 파일을 건드려야 해?"라고 물으면 잘 알려줬다.

rrweb 리플레이 구현

12월 24일에는 rrweb 재생 기능을 본격적으로 구현했다(devtools-frontend PR #4). DevTools 프론트엔드 안에 rrweb Player를 임베드하고, 녹화된 이벤트를 받아서 재생하는 패널을 만들었다.

녹화 시작/정지 버튼, 타임라인 슬라이더, 재생 속도 조절 같은 기본 컨트롤을 넣었다. rrweb Player가 워낙 잘 만들어져 있어서 UI 쪽은 그렇게 어렵지 않았다.

IndexedDB로 이벤트 저장

rrweb 이벤트를 메모리에만 들고 있으면 페이지를 새로고침하는 순간 날아간다. 그래서 IndexedDB에 저장하기로 했다.

브라우저 내장 스토리지 중에서 용량이 넉넉한 건 IndexedDB뿐이다. localStorage는 5MB 제한이 있어서 DOM 스냅샷 몇 개만 넣어도 터진다. IndexedDB는 디스크 공간에 따라 수백 MB까지 쓸 수 있으니 한참 기록해도 괜찮다.

다만 그대로 저장하면 금방 용량이 커지니까, 저장 전에 압축을 한 번 거치도록 했다. 재생할 때는 꺼내서 압축 해제한 뒤 rrweb Player에 넘긴다.

DevTools를 켜기 전의 기록도 남기고 싶다

여기서 한 가지 더 욕심이 났다. Inspector를 열기 전에 이미 일어난 사용자 액션도 기록하고 싶었다. 예를 들어 페이지 로드 직후의 콘솔 로그나 네트워크 요청은, Inspector를 나중에 열면 그 시점부터만 보인다.

그래서 클라이언트가 WebSocket에 연결되기 전부터 CDP 이벤트를 IndexedDB 버퍼에 쌓아두고, 나중에 Inspector가 연결되면 그 버퍼를 한꺼번에 전송하는 방식을 만들었다. 이게 나중에 오프라인 리플레이 기능의 기반이 됐다.

크리스마스 이브의 성과

프로젝트 시작 5일째, "기록하고 재생한다"는 핵심 기능이 동작하기 시작했다. 웹 페이지에서 뭔가를 하면 rrweb이 녹화하고, Inspector의 Session Replay 패널에서 그걸 다시 볼 수 있다. CDP 이벤트(콘솔, 네트워크)도 같이 기록되니까 당시 상황을 통째로 재현할 수 있다.

배민이 했던 "기록방" 기능의 프로토타입이 나온 셈이다. 크리스마스 이브에 코딩하고 있는 나를 돌아보면 좀 안쓰럽긴 한데, 결과물이 나오니까 기분은 좋았다.

1줄 요약

rrweb을 연동해서 DOM 녹화/재생 기능을 구현하고, IndexedDB에 이벤트를 저장해서 Inspector를 열기 전의 기록도 남길 수 있게 했다.