Linear가 빠른 이유 — 네트워크를 UI에서 뺀 아키텍처

· 4분 읽기
목차

Linear가 빠른 건 최적화를 잘 해서가 아니다. 네트워크를 UI의 임계 경로에서 완전히 빼버렸기 때문이다.

Linear의 local-first 아키텍처 — 브라우저 내 DB와 동기화 엔진이 네트워크 지연을 UI에서 제거하는 구조

Linear는 이슈 트래커를 중심으로 둔 프로젝트 관리 도구다. 처음 켜는 순간 “왜 이렇게 빠르지?”가 된다 — 이슈를 클릭하면 몇 밀리초 안에 화면에 반영된다. 보통의 웹 앱이라면 서버에 요청을 보내고 응답을 기다린 뒤 화면을 그린다. Linear는 그 사이클을 기다리지 않는다.

performance.dev의 기술 분석은 그 비결을 한 문장으로 잡는다. UI 업데이트에 필요한 모든 데이터가 로컬에 있고, 서버는 동기화·백업·충돌해결을 담지, UI 응답 자체에는 관여하지 않는다. 속도는 ‘튜닝’의 결과가 아니라 아키텍처 선택의 결과다.

임계 경로에서 네트워크를 뺀다

핵심 개념은 임계 경로(critical path)다. 사용자가 버튼을 눌렀을 때 화면이 바뀌기까지 거쳐야 하는 단계들. 보통 웹 앱의 임계 경로엔 네트워크가 있다 — 요청을 보내고, 서버가 처리하고, 응답이 돌아오고, 그제야 화면을 갱신한다. 이 왕복이 수십~수백 ms다.

Linear는 이 왕복을 임계 경로에서 뺀다. 클릭하면 로컬 DB에 먼저 쓰고, 로컬 데이터로 즉시 화면을 갱신한다. 서버 동기화는 그 뒤에서 비동기로 일어난다. 사용자는 네트워크 상태와 무관하게 ms 단위 응답을 받는다. “오프라인에서도 되는 앱”이 아니라, 온라인이어도 네트워크를 기다리지 않는 앱이다.

이게 가능하려면 세 가지가 같이 돌아야 한다.

1. Local-first 동기화 엔진

데이터를 로컬에 두고 서버와 동기화하는 구조를 local-first라고 부른다. 읽기·쓰기는 로컬에서 끝나고, 서버는 그 변경을 전파하고 백업하고 충돌을 푸는 역할만 한다.

Linear는 2019년에 이 동기화 엔진을 직접 만들었다. Hacker News에서 Linear 팀이 남긴 설명에 따르면, 당시엔 쓸 만한 옵션이 없어서 자체 엔진을 구축했다고 한다. 남들이 API 서버 위에 클라이언트를 얹을 때, Linear는 클라이언트와 서버가 동등한 동기화 노드가 되는 구조를 선택한 셈이다.

이 선택이 비용이다. 동기화 엔진을 직접 만든다는 건, 충돌 해결·부분 실패·재연결·버전 벡터 같은 분산 시스템 문제를 떠안는 일이다. Linear가 빠른 건 이 비용을 2019년에 치렀기 때문이다.

2. MobX observable

데이터가 로컬에 있어도, 화면을 갱신하는 방식이 무거우면 의미가 없다. Linear는 상태 관리에 MobX를 쓴다.

MobX의 핵심은 observable(관찰 가능한 상태)이다. 상태를 관찰 대상으로 만들어두면, 상태가 바뀔 때 그 상태에 의존하는 컴포넌트만 다시 그려진다. React의 기본 상태 모델이 컴포넌트 트리를 타고 내려가며 갱신을 결정하는 것과 달리, MobX는 의존성을 자동으로 추적해서 정확히 필요한 컴포넌트만 건드린다.

이슈 하나의 담당자가 바뀌었을 때, 그 이슈를 보고 있는 컴포넌트만 재렌더되고 나머지 화면은 가만히 있는다. 로컬 DB에서 꺼낸 데이터를 화면에 반영하는 비용이 최소화되는 구조다. local-first로 데이터를 빠르게 가져와도, 갱신이 무거우면 그 이점이 사라진다. MobX는 그 마지막 단계를 가볍게 만든다.

3. IndexedDB에 앱 전체 DB

세 번째는 IndexedDB다. 브라우저에 내장된 영속 저장소인데, Linear는 앱 전체 데이터베이스를 여기에 둔다.

새로고침해도 데이터가 남는다. 첫 로딩 때 서버에서 전부 가져오는 게 아니라, 로컬 DB에서 읽어 즉시 화면을 구성한다. 그래서 Linear의 첫 페이지 로드가 약 50ms 수준이라는 분석이 나온다. 서버 왕복이 첫 로딩 경로에 없으니까.

IndexedDB는 파일 캐시와 다르다. 키-값이 아니라 인덱스와 트랜잭션을 갖춘 DB라, 복잡한 질의를 로컬에서 할 수 있다. “내가 담당자인 이슈 중 상태가 진행 중인 것” 같은 필터도 네트워크 없이 로컬에서 끝난다.

세 결정이 하나의 시스템이다

여기서 중요한 관찰이 있다. 세 가지(local-first sync·MobX·IndexedDB)는 각각 쓴다고 빨라지지 않는다.

  • local-first 동기화만 있고 갱신이 무거우면, 로컬 응답은 빨라도 화면 반영이 늦는다.
  • MobX로 갱신은 가벼운데 데이터가 서버에만 있으면, 결국 네트워크를 기다려야 한다.
  • IndexedDB는 있지만 동기화가 없으면, 새로고침은 빠르지만 두 기기 간 데이터가 어긋난다.

byteiota의 분석은 이걸 “세 가지 맞물리는 결정이 하나의 시스템”으로 본다. 하나라도 빼면 50ms가 무너진다. Linear의 속도는 트릭이 아니라, 세 축이 함께 설계된 결과다. 거기에 키보드 우선 설계(keyboard-first)까지 얹어 상호작용 지연까지 줄인다.

로컬 우선의 극단

이 흐름은 며칠 전 작고 로컬한 도구 4종에서 살핀 가치관과 같은 방향이다. OpenLogi가 마우스 설정을 계정·클라우드 없이 로컬에서 처리한다면, Linear는 앱 전체 데이터를 로컬에 두고 서버를 동기화 레이어로 내린다. 같은 “로컬 우선” 철학의 도구와 아키텍처 양끝.

차이는 규모다. OpenLogi는 단일 기기의 하드웨어 설정을 로컬에서 끝낸다. Linear는 여러 기기와 여러 사용자가 동시에 쓰는 협업 앱에서도 로컬 우선을 유지한다. 그러려면 동기화·충돌해결·오프라인 일관성이라는 분산 시스템 문제를 풀어야 한다. Linear가 2019년에 자체 엔진을 만든 이유다.

비용을 솔직하게

이 아키텍처는 공짜가 아니다.

  • 동기화 복잡도 — 두 사람이 같은 이슈를 동시에 고치면 충돌이 난다. last-write-wins인지, CRDT인지, 앱 수준 병합인지 정책이 필요하다.
  • 일관성 — 로컬이 곧 진실이면, 일시적으로 기기 간 데이터가 어긋난다. 사용자에게 “방금 바뀐 게 아직 안 보일 수 있다”는 UX가 필요할 수도 있다.
  • 초기 투자 — 2019년에 자체 sync 엔진을 만든 건, 당장 출시가 늦어지는 결정이었다. 팀이 감당할 수 있는 베팅이어야 한다.

Linear의 50ms는 이 비용을 이미 치른 결과다. 모든 앱이 이 구조를 가져야 하는 건 아니다. 읽기 위주·실시간 정합성이 중요한 서비스라면 전통적인 서버 우선이 더 단순할 수 있다.

속도는 아키텍처다

정리하면, Linear의 빠름은 “잘 튜닝해서”가 아니다.

  • 네트워크를 UI 임계 경로에서 뺀다 — 응답은 로컬에서 끝나고 동기화는 뒤따른다.
  • 세 축이 시스템 — local-first sync·MobX·IndexedDB가 함께 설계되어야 50ms가 나온다.
  • 비용은 분산 시스템 — 동기화와 충돌을 2019년에 직접 풀었기에 가능한 구조다.

빠른 앱을 만들고 싶을 때 가장 먼저 물어야 할 질문은, “이 응답 경로에 네트워크가 있어야 하는가”다. Linear의 대답은 “아니다”였고, 그 대답을 지키기 위해 아키텍처 전체를 다시 짰다. 속도는 그 설계의 부산물이다.

최적화는 기존 경로를 빠르게 하는 일이다. Linear는 경로 자체를 바꿨다 — 그래서 다르게 빠르다.

이어서 읽기