Linear 성능 아키텍처: 기술적 분석
Linear는 전통적인 클라이언트-서버 관계를 뒤집음으로써 인지되는 속도를 달성합니다. UI가 모든 작업에 대해 서버의 응답을 기다리는 대신, Linear는 브라우저를 기본 데이터베이스로 취급하며, 변경 사항(mutations)이 로컬 스토어에 즉시 적용되고 서버와 비동기적으로 동기화되는 로컬 퍼스트(local-first) 아키텍처를 사용합니다.
로컬 퍼스트 데이터 아키텍처
Linear 성능의 핵심은 사용자 상호작용의 임계 경로(critical path)에서 네트워크 요청을 제거하는 것입니다. 애플리케이션 상태를 IndexedDB에 저장하고 이를 인메모리 MobX observable graph로 하이드레이션(hydrating)함으로써, UI는 데이터를 로컬에서 읽고 씁니다.
브라우저 기반 데이터베이스
전통적인 CRUD 애플리케이션에서는 사용자 작업이 HTTP 요청, 서버 쿼리, 그리고 그에 따른 UI 리페인트(repaint)를 트리거하여 종종 로딩 스피너를 유발합니다. Linear는 이 루프를 로컬 퍼스트 접근 방식으로 대체합니다:
- 로컬 뮤테이션(Local Mutation): 사용자가 이슈를 업데이트하면, 변경 사항은 인메모리 데이터스토어에 즉시 적용됩니다.
- 비동기 동기화(Asynchronous Sync): 뮤테이션은 IndexedDB의 내구성이 있는 트랜잭션 큐에 기록된 후, 백그라운드에서 배치(batch) 처리되어 서버로 전송됩니다.
- 서버 브로드캐스트(Server Broadcast): 서버는 변경 사항을 확인하고 WebSockets를 통해 다른 클라이언트에 델타(deltas)를 브로드캐스트합니다.
MobX를 통한 세밀한 리렌더링
대규모 업데이트 중에 UI가 지연되는 것을 방지하기 위해, Linear는 MobX를 사용하여 변경된 필드에 의존하는 특정 컴포넌트만 리렌더링되도록 보장합니다. 모든 모델의 모든 속성이 각각의 observable이므로, 단일 이슈 필드의 변경은 전체 리스트 리렌더링이 아닌 "one cell" 리렌더링을 트리거합니다. 이를 통해 여러 사용자가 동시에 워크스페이스를 편집하더라도 애플리케이션이 매끄럽게 유지될 수 있습니다.
첫 로딩 최적화
Linear는 공격적인 코드 분할(code splitting), 프리로딩(preloading), 그리고 인라인 앱 쉘(inlined app shell)을 조합하여 초기 페이지 로딩이 즉각적인 것처럼 느껴지게 만듭니다.
빌드 타임 최적화
Linear는 클라이언트에 전달되는 JavaScript와 CSS의 양을 줄이기 위해 빌드 파이프라인을 발전시켜 왔습니다(Parcel에서 Rollup, Vite, 그리고 현재의 Rolldown으로 이동).
주요 전략은 다음과 같습니다:
- 레거시 지원 제거: 현대적인 브라우저를 타겟팅하여 폴리필(polyfills)과 ES5 트랜스파일링을 제거합니다.
- 공격적인 코드 분할: 애플리케이션을 수백 개의 라우트 레벨 청크(chunks)로 나눕니다. 약 ~3KB보다 큰 모든 npm package는 자체 청크로 분할되어, 하나의 의존성을 업데이트하더라도 전체 vendor cache가 무효화되지 않도록 합니다.
병렬 로딩 및 프리캐싱
브라우저가 스크립트를 순차적으로 가져오는 "워터폴(waterfall)" 효과를 피하기 위해, Linear는 HTML head에 <link rel="modulepreload">를 사용합니다. 이를 통해 브라우저는 엔트리 스크립트가 실행되기 전에도 임계 청크들을 병렬로 가져올 수 있습니다.
또한, 서비스 워커(service worker)는 첫 로딩 이후 백그라운드에서 약 1,200개의 해시된 에셋(라우트 청크, 아이콘, 폰트)을 프리캐싱합니다. 이는 후속 네비게이션이 네트워크를 완전히 건너뛰고 앱이 오프라인에서도 작동할 수 있도록 보장합니다.
인라인 앱 쉘 및 즉각적인 렌더링
Linear는 로딩 상태를 그리는 데 필요한 핵심 스타일을 <head>에 직접 인라인화하여 CSS에 대한 초기 네트워크 요청을 제거합니다. 또한 인라인 부트 스크립트를 사용하여 localStorage에서 사용자 설정(테마, 사이드바 너비)과 인증 상태를를 읽습니다.
결정적으로, Linear는 "먼저 렌더링하고, 나중에 인증한다"는 전략을 채택합니다. 만약 localStorage.ApplicationStore가 존재한다면, 앱은 사용자가 로그인된 상태라고 가정하고 IndexedDB에서 즉시 전체 경험을 렌더링하며, 백그라운드에서 서버를 통해 세션 토큰을 검증합니다. 만약 세션이 만료되었다면, 첫 번째 요청이 실패한 후에만 사용자가 로그인 페이지로 리단렉션됩니다.
디자인 및 애니메이션 원칙
엔지니어링 속도는 작업을 완료하는 데 필요한 물리적 및 인지적 노력을 줄이는 디자인 선택에 의해 보완됩니다.
키보드 우선 네비게이션
Linear는 포괄적인 단축키 시스템과 글로벌 커맨드 팔레트(⌘ K)를 통합합니다. 커맨드 팔레트가 빠른 이유는 서버 요청을 하는 대신 로컬 MobX 객체 풀을 검색하기 때문이며, 이를 통해 네비게이션과 작업 실행을 거의 즉각적으로 만 수듭니다.
GPU 가속 애니메이션
높은 프레임 레이트를 유지하기 위해, Linear는 애니메이션을 주로 GPU가 처리하고 레이아웃 재계산을 트리거하지 않는 합성 속성(composited properties)—주로 transform과 opacity—로 엄격히 제한합니다.
Linear 또한 비대칭 타이밍을 사용합니다: 요소가 나타날 때는 즉시 나타나지만, 사라질 때는 약 ~150ms 동안 페이드 아웃됩니다. 이는 인터페이스가 빠릿빠릿하게 느껴지도록 하면서도 필요한 공간적 맥락을 제공합니다.
기술적 트레이드오프 및 커뮤니티 관점
로컬 퍼스트 아키텍처는 상당한 속도를 제공하지만, 데이터 일관성 및 충돌 해결에 관한 복잡성을 야기합니다.
동기화 문제
커뮤니티 논의에서는 견고한 동기화 엔진을 구축하는 것이 결코 쉽지 않다는 점을 강조합니다. 잠재적인 문제는 다음과 같습니다:
- 충돌 해결(Conflict Resolution): 두 명의 오프라인 사용자가 동일한 이슈를를 템플릿으로 편집하거나 한 명이 이슈를 삭제하는 동안 다른 한 명이 편집하는 시나리오를 처리하는 문제.
- 일관성(Consistency): 사용자의 업데이트가 팀에 도달했는지 확신할 수 없는 "동기화 지연"의 위험.
- 상태 관리(State Management): 사용자가 오프라인 상태가 되었다가 다시 연결될 때 발생하는 스키마 드리프트(schema drift)와 비즈니스 로직 변경을 관리하는 어려움.
사용자 경험의 차이
일부 사용자들은 UI의 낙관적(optimistic) 특성이 데이터가 백그라운드에서 동기화 중이라는 시각적 표시가 없을 때 혼란을 줄 수 있다고 보고했습니다. 다른 사용자들은 앱이 Jira와 같은 레거시 도구보다 훨씬 빠르지만, CPU 스파이크나 상호작용을 차단하는 "동기화 중" 잠금 상태를 경험할 수 있다고 언급했습니다.