Linear Performance Architecture: A Technical Breakdown
Linearは、従来のクライアント・サーバー関係を逆転させることで、知覚される速度を実現しています。UIがサーバーの応答を待つのではなく、ブラウザをプライマリ・データベースとして扱い、ローカルファースト(local-first)アーキテクチャを採用することで、ミューテーション(mutations)をローカルストアに即座に適用し、サーバーと非同期に同期させる仕組みになっています。
Local-First Data Architecture
Linearのパフォーマンスの核心は、ユーザー操作のクリティカル・パスからネットワーク・リクエストを排除することにあります。アプリケーションの状態をIndexedDBに保存し、それをメモリ内のMobX observable graphにハイドレーション(hydrating)することで、UIはデータをローカルで読み書きします。
Browser-Based Database
従来のCRUDアプリケーションでは、ユーザーの操作がHTTPリクエスト、サーバー・クエリ、そしてその後のUIの再描画をトリガーし、多くの場合ローディング・スピナーが表示されます。Linearは、このループをローカルファーストのアプローチに置き換えています。
- Local Mutation: ユーザーがissueを更新すると、変更はメモリ内のデータストアに即座に適用されます。
- Asynchronous Sync: ミューテーションはIndexedDB内の永続的なトランザクション・キューに書き込まれ、その後バックグラウンドでバッチ処理されてサーバーへプッシュされます。
- Server Broadcast: サーバーは変更を確認し、WebSocketsを介して他のクライアントへデルタ(deltas)をブロードキャストします。
Granular Re-renders with MobX
大規模な更新中にUIがラグを引き起こすのを防ぐため、LinearはMobXを使用して、変更されたフィールドに依存する特定のコンポーネントのみが再描画されるようにしています。すべてのモデルのすべてのプロパティが独自のobservableであるため、単一のissueフィールドの変更は、リスト全体の再描画ではなく「1セル」の再描画をトリガーします。これにより、複数のユーザーが同時にワークスペースを編集していても、アプリケーションはスムーズな状態を維持できます。
Optimizing the First Load
Linearは、アグレッシブなコード分割、プリロード、およびインライン化されたapp shellを使用して、初期ページのロードを瞬時に感じさせるように設計されています。
Build-Time Optimizations
Linearは、クライアントに送信されるJavaScriptとCSSの量を削減するために、ビルド・パイプライン(ParcelからRollup、次にVite、そして現在はRolldownへ)を進化させてきました。主な戦略は以下の通りです:
- Dropping Legacy Support: モダンなブラウザをターゲットにすることで、ポリフィル(polyfills)やES5へのトランスパイルを排除しています。
- Aggressive Code Splitting: アプリケーションを数百のルート・レベルのチャンクに分割しています。~3KBを超える各npm packageは独自のチャンクに分割され、1つの依存関係の更新がベンダー・キャッシュを無効化しないように設計されています。
Parallel Loading and Precaching
ブラウザがスクリプトを sequentially 順次次々と取得する「ウォーターフォール」効果を避けるため、LinearはHTMLのhead内で<link rel="modulepreload">を使用しています。これにより、エントリ・スクリプトが実行される前に、ブラウザがクリティカルなチャンクを並列に取得できるようになります。
さらに、サービス・ワーカー(service worker)は、初回ロード後にバックグラウンドで約1,200個のハッシュ化されたアセット(ルート・チャンク、アイコン、フォント)をプリキャッシュします。これにより、後続のナビゲーションはネットワークを完全にスキップし、アプリがオフラインでも機能し続けることが可能になります。
Inlined App Shell and Immediate Rendering
Linearは、CSSの初期ネットワーク・リクエストを排除するため、描画に必要なクリティカルなスタイルを<head>内に直接インライン化しています。また、ユーザーの好みの設定(テーマ、サイドバーの幅)や認証状態を読み取るために、インラインのブート・スクリプトを使用しています。
決定的な要素として、Linearは「まず描画し、次に認証する(render first, authenticate second)」戦略を採用しています。localStorage.ApplicationStoreが存在する場合、アプリはユーザーがログイン済みであると仮定してIndexedDBから即座にフル・エクスペリエンスを描画し、セッション・トークンはバックグラウンドでサーバーを介して検証します。セッションが古い場合は、ユーザーは最初のリクエストが失敗した後にのみログイン画面へリダイレクトされます。
Design and Animation Principles
エンジニアリングの速度は、タスクを完了するための物理的および認知的負荷を軽減するデザインの選択によって補完されています。
Keyboard-First Navigation
Linearは、包括的なショートカット・システムとグローバル・コマンド・パレット(⌘ K)を統合しています。コマンド・パレットは、サーバー・リクエストを行うのではなく、ローカルのMobX object poolを検索するため、ナビゲーションやアクションの実行が極めて高速です。
GPU-Accelerated Animations
高いフレームレートを維持するため、Linearはアニメーションをコンポジット・プロパティ(composited properties)—主にtransformとopacity—に厳格に制限しています。これらはGPUによって処理され、レイアウトの再計算をトリガーしません。
Linearはまた、非対称なタイミングを使用しています。要素は呼び出された瞬間に表示され、消えるときは約150msかけてフェードアウトします。これにより、インターフェースがキビキビとした感覚を与えつつ、必要な空間的コンテキストを提供します。
Technical Trade-offs and Community Perspectives
ローカルファースト・アーキテクチャは、大幅な速度向上をもたらす一方で、データの整合性と競合解決に関する複雑さをもたらします。
Sync Challenges
コミュニティの議論では、堅牢な同期エンジンを構築することは容易ではないことが強調されています。潜在的な問題は以下の通りです:
Conflict Resolution: 2人のオフライン・ユーザーが同じissueを編集したり、一人がissueを削除し、もう一人がそれを編集しようとしたりするシナリオをハンドルする。
Consistency: ユーザーが自分の更新がチームに届いているか確信が持てない「同期ラグ」のリスク。
State Management: ユーザーがオフラインになり、再接続した際のスキーマ・ドリフトやビジネスロジックの変更を管理する難しさ。
User Experience Variance
一部のユーザーからは、UIの楽観的(optimistic)な性質が、バックグラウンドでデータが同期中であることを示す視覚的インジケーターがない場合に混乱を招くことがあると報告されています。また、Jiraのようなレガシー・ツールと比較してアプリは大幅に高速ですが、CPUのスパイクや、インタラクションをブロックする「同期中」のロックが発生することがあると指摘されています。