Linear는 어떻게 이렇게 빠른가? — 브라우저 안 데이터베이스의 비밀
도구 하나가 이렇게 빨라도 되는가? Linear는 이슈 한 줄을 바꿀 때도 스피너를 띄우지 않는다. 클릭과 동시에 화면이 갱신되고, 백그라운드에서 서버가 그 변경을 확인한다. 이 체감 속도는 마법이 아니라 브라우저 안 데이터베이스, 로컬 우선 동기화, 시스템 설계의 결과다. 오늘은 그 비밀을 풀어본다.
IndexedDB가 UI의 단일 진실 공급원
일반적인 CRUD 웹앱은 클릭마다 HTTP 요청을 보내고, 서버의 데이터베이스를 조회한 뒤 응답을 받아 다시 그린다. 이 과정만 수백 밀리초가 걸린다. 그 사이 사용자는 스피너와 멈춘 UI를 본다.
Linear는 다르다. UI가 읽는 실제 데이터베이스가 브라우저의 IndexedDB에 있다. 변경은 로컬에 먼저 적용되고, 서버는 WebSocket으로 다른 클라이언트에 델타만 브로드캐스트한다. 사용자는 네트워크를 기다리지 않는다. 가장 큰 병목인 데이터 전송이 사용자에게 보이지 않게 사라진다.
// Linear의 낙관적 업데이트 패턴issue.title = "Faster app launch"; // 메모리 내 MobX 갱신issue.save(); // 동기화 엔진이 트랜잭션 큐에 적재
issue.save()는 서버에 즉시 가지 않는다. 트랜잭션 큐에 들어가고, 동기화 엔진이 배치로 플러시한다. UI는 로컬 변경을 기준으로 동기적으로 다시 그려진다. 데이터 동기화는 백그라운드로 빠진다.
동기화 엔진의 세 가지 축
Linear의 속도는 단일 요소가 아니라 세 가지가 맞물린 결과다.
첫째, 데이터가 이미 있다. 앱 부팅 시 서버에서 워크스페이스를 가져오지 않는다. IndexedDB에 저장된 데이터를 메모리 내 MobX 객체 풀로 수화한다. "loading issues" 상태 자체가 존재하지 않는다. 10,000개 이슈 워크스페이스도 100개 워크스페이스와 거의 같은 속도로 부팅된다. 시작 비용이 데이터 양이 아니라 워크스페이스 구조를 따라간다.
둘째, 변경은 네트워크를 기다리지 않는다. 이슈 상태를 바꾸면 MobX observable이 UI를 갱신하고, IndexedDB의 트랜잭션 큐에 기록하고, 서버 전송 큐에 추가한다. 이 모든 것이 한 프레임 안에 끝난다. 사용자는 재시도와 롤백을 의식하지 못한다.
셋째, 하나의 델타, 하나의 셀. 서버가 변경을 확인하면 작은 JSON envelope이 돌아온다. 클라이언트는 MobX observable에 값을 쓴다. 모든 모델 속성은 observable이고, 컴포넌트는 observer()로 감싸여 있다. MobX는 어떤 컴포넌트가 어떤 필드를 읽는지 정확히 안다. 50개 이슈 업데이트는 50개 셀만 다시 그릴 뿐, 부모 목록 전체를 리렌더링하지 않는다.
첫 로드를 즉각적으로 느끼게 만드는 빌드 파이프라인
빠른 체감은 런타임이 아니라 빌드 타임에서 시작한다. Linear는 Parcel → Rollup → Vite → Rolldown 순서로 빌드 파이프라인을 다시 썼다. 그 결과는 압도적이다.
• 전송 코드 50% 감소
• 압축 후 크기 30% 감소
• 콜드 캐시 페이지 로드 10~30% 개선
• Safari에서 Time-to-first-paint 59% 감소
• 메모리 사용량 70~80% 감소
핵심은 특정 번들러가 아니라 레거시 브라우저 제거, 네이티브 ESM 전환, 적극적 코드 분할이다. 약 21MB의 JavaScript를 수백 개 라우트 수준 청크로 쪼개고, 의 modulepreload로 병렬 요청한다. 콜드 로드 타임라인이 순차 폭포에서 단일 병렬 배치로 바뀐다.
추가로 서비스 워커의 precache가 다음에 필요할 것을 백그라운드에서 준비한다. 로그인 화면에 도달한 뒤 몇 초 안에 전체 앱이 캐시에 들어가고, 이후 탐색은 네트워크를 완전히 건너뛴다.
디자인이 엔지니어링을 살린다
속도만큼 중요한 것이 사용자 동선이다. 가장 빠른 액션 경로가 마우스 3개 메뉴와 클릭을 요구하면, 내부 엔진이 아무리 빨라도 그 단계는 비용이 된다.
Linear의 거의 모든 액션에는 단축키가 있다. ⌘ K 한 번이면 이슈, 프로젝트, 라벨, 상태 변경, 설정까지 검색된다. command palette는 서버가 아니라 로컬 MobX 객체 풀을 뒤지므로 즉시 반응한다. 마우스 사용자도 같은 기능을 쓸 수 있어 진입 장벽은 없다.
/* Linear의 GPU 친화적 트랜지션 */.row:hover {background-color: var(--color-bg-hover);transition: background-color 0.12s;}.icon-arrow {transform: translateX(0);transition: transform 0.15s;}
width, height, margin 같은 layout-triggering 속성은 애니메이션하지 않는다. transform과 opacity만 움직인다. 200ms 동안 매 프레임 layout을 재계산하는 일은 없다. 짧고 즉각적인 duration이 jank를 막는다.
그래서 우리도 따라 할 수 있는가
대부분의 앱은 자체 동기화 엔진을 만들 필요가 없다. TanStack Query와 SWR의 낙관적 업데이트만으로도 상당히 가까운 체감 속도를 낼 수 있다. 핵심은 네트워크를 사용자에게 보이지 않게 만드는 설계 의지다.
UI 반응성은 네트워크 지연에 의존하지 않아야 한다. 서버 응답 속도보다 인터페이스 반응 속도가 사용자가 느끼는 속도를 결정한다. 그 사실 하나만 기억해도 우리 앱은 한 단계 빨라진다.
'자동화&툴 리뷰' 카테고리의 다른 글
| HTMX가 너무 멋져서 직접 만들었다 — 서버 렌더링 HTML로 돌아가는 미니멀 프런트엔드 (0) | 2026.06.10 |
|---|---|
| 로컬 회의녹취 Decision Wiki — 외부 AI 못 쓰는 환경의 해결책 (0) | 2026.06.10 |
| 중독, 수감, 중범죄 전과 이후 제로에서 다시 쌓아 올리기 — Hasura로 돌아온 개발자 Gavin의 재건기 (0) | 2026.06.09 |
| Google, xAI 데이터 센터의 컴퓨팅 용량 사용료로 매달 9억2천만 달러를 SpaceX에 지불할 예정 (1) | 2026.06.08 |
| OpenLogi - Rust로 작성된 Logitech Options+ 대체 오픈소스 완벽 가이드 (0) | 2026.06.08 |