3 ポイント 投稿者 GN⁺ 4 시간 전 | 1件のコメント | WhatsAppで共有
  • Linear は、課題管理の作業をブラウザ内データベースとローカル優先同期で処理し、課題の更新が数ミリ秒以内にUIへ反映される生産性ツール
  • UIが読み取る実際のデータベースは IndexedDB にあり、変更はまずローカルに適用された後、サーバーへ非同期送信され、WebSocketで差分が再配信される構造
  • 初回ロードでは、少量のJavaScript・CSS転送、積極的なコード分割、modulepreload、サービスワーカーのプリキャッシュ、インラインのアプリシェルによってネットワーク待機を減らす読み込み戦略
  • 同期エンジンは IndexedDB のデータを MobX オブジェクトプールへハイドレートし、変更をトランザクションキューに保存し、フィールド単位の observable な状態によって必要なセルだけを再レンダリングする実装
  • 速く感じられる体感速度は、キーボード中心の入力、グローバルコマンドパレット、GPUフレンドリーなアニメーション、短い遷移時間までを含めた システム設計 の結果

ブラウザ内のデータベース

  • 従来のCRUD Webアプリは、ユーザーのクリック後にHTTPリクエスト、サーバーのデータベース参照、レスポンス受信、ブラウザの再描画を経るため、数百ミリ秒のあいだスピナー・スケルトン・停止したUIが発生
  • Linearは、UIが読み取る実際のデータベースをブラウザの IndexedDB に置き、変更はまずローカルに適用した後サーバーへ非同期送信し、サーバーは WebSocket で他のクライアントに差分をブロードキャスト
  • 高速なWebアプリで最大のボトルネックはネットワークであり、クライアントとサーバー間のデータ転送には数百ミリ秒のコストが発生
  • Linearの中核フローは、ネットワークリクエストをユーザーに見えなくし、可能な限りローディング状態をなくす方式
// Linear
issue.title = "Faster app launch";
issue.save();
  • issue.title = "Faster app launch" はメモリ内データストアを更新し、Linearでは MobX observable を使用
  • issue.save(); は同期エンジンがバッチ処理してサーバーへフラッシュするトランザクションをキューに入れる動作
  • UIはローカルメモリの変更を基準に同期的に再レンダリングされ、データ同期はバックグラウンドで進行するためスピナーは不要
  • Tuomas は2024年のカンファレンスで、Linearで最初に書いたコードが同期エンジンだったと話しており、スタートアップでは一般的でないアプローチだったと表現
  • ほとんどのアプリはLinearのように独自の同期エンジンを作る必要はなく、TanStack QuerySWR の楽観的更新だけでも、かなり近い体感速度を実現可能
  • 楽観的リクエストは、不要なスピナーの除去、即時の状態更新、バックグラウンド検証、必要時のロールバックを通じて高い改善効果を提供
  • UIの応答性はネットワーク遅延に依存すべきではなく、ユーザーが感じる速度はサーバー応答速度よりインターフェースの応答速度によって決まる
  • Linearのスタックをのぞく

    • Linearは React、TypeScript、MobX、Postgres、CDN といったシンプルなスタックの上に構築
    • フロントエンドでは React と react-dom、MobX、TypeScript、Rolldown-Vite と plugin-react-oxc、ProseMirror と y-prosemirror、Radix UI primitives、Emotion と StyleX、Comlink、idbgraphql-request、Sentry、Inter Variable を使用
    • バックエンドでは Node.js と TypeScript、Cloud SQL 上の PostgreSQL、Memorystore Redis、turbopuffer、GCP の Kubernetes、Cloudflare Workers を使用
    • デスクトップクライアントは Electron ベースで、モバイルは iOS 向け Swift と Kotlin により別途フル再実装
    • マーケティングサイトでは Next.js、styled-components、インライン SVG sprite を使用
    • Linearはクライアントサイドレンダリングを維持しており、適切なアーキテクチャとデザインがあればCSRでも即時に感じられることを示す事例
    • アプリ全体をクライアントサイドに保つことで、サーバー・クライアントの区別、window へアクセス可能かどうか、キャッシュヘッダー設定といった複雑さを減らすシンプルなメンタルモデルを確保

初回ロードを即時に感じさせる

  • 生産性ツールでは、実際の作業を始めるまでにかかる時間は重要な細部要素
  • クライアントサイドアプリの初期ロードは、index.html のリクエスト、JavaScript と CSS のリクエスト、認証処理、アプリ表示のための API リクエストという順序で遅くなりうる
  • Linearのバンドラーフロー: Parcel, Rollup, Vite, Rolldown

    • 即時の体感速度はランタイム以前のビルドタイムから始まり、高速なロードのために送信する JavaScript と CSS の量を減らす作業が重要
    • Linear はビルドパイプラインを Parcel → Rollup → Vite → Rolldown の順に書き直しており、各移行は JavaScript・CSS 量の削減と開発者体験の改善を目標としている
    • Linear ブログ基準の改善数値
    • 転送コード 50% 減少
    • 圧縮後サイズ 30% 減少
    • コールドキャッシュでのページロード 10〜30% 改善
    • Safari での active-issues ビューの Time-to-first-paint 59% 減少
    • メモリ使用量 70〜80% 減少
    • 改善の大部分は最新ブラウザのみを対象にする決定、より優れた dead-code elimination、積極的なコード分割の組み合わせから生まれている
    • レガシーサポートの終了は、polyfill、ES5 トランスパイル、nomodule フォールバックの削除という大きな利点につながる
    • Linear は最適化後も約 21MB の minified JavaScript を転送しているが、これを数百のルートレベルチャンクに積極的に分割し、必要なときに取得する方式を採っている
    • 重要なのは特定のバンドラーの選択ではなく、レガシーブラウザの排除、ネイティブ ESM への移行、積極的なコード分割
    • こうした段階が積み重なり、Linear の初回ロード JavaScript はおおよそ半分に減り、ビルド時間は一桁レベルではなく一桁上のオーダーで短縮された
  • 初期ロード後のプリロード

    • JavaScript を小さなチャンクに分けると、各チャンクが別のチャンクを import するウォーターフォール型ロードの問題が生じる
    • Linear は JavaScript 実行前にブラウザが全体の一覧を見てリクエストを並列に開始できるようにし、entry script が最初の import に到達する時点で関連チャンクがすでにキャッシュに入っているよう構成している
    • <head>modulepreload と entry script の crossorigin の値を一致させ、ブラウザが preload と import を別リソースとして扱わず、キャッシュされた fetch を再利用するようにしている
    • コールドロードのタイムラインは逐次的なウォーターフォールから単一の並列バッチに変わり、ネットワーク処理は依然として存在するが一度に実行される
    • ユーザーがログインページに最初に到達した時点でバックグラウンドでこの作業を実行し、数秒後にはアプリ全体をキャッシュして即時提供できる
    広告
  • さらに高速化しオフライン機能も実現するサービスワーカー

    • ユーザーがまだ訪れていないビューのルートレベルチャンクは、サービスワーカーがバックグラウンドでキャッシュする
    • サービスワーカーはソースに組み込まれた precache manifest を持ち、約 1,200 個のハッシュ化された asset がルートチャンク、アイコン、フォントを含んでいる
    • ログイン画面に到達してから数秒以内に、アプリ全体がキャッシュに入る構造
    • その後のナビゲーションではネットワークを完全に迂回し、サービスワーカーが HTTP キャッシュを経由せず自身のキャッシュから直接応答する
    • ローカル優先の同期エンジンと IndexedDB にすでに保存されたユーザーデータが組み合わさることで、Linear はオフラインでも利用可能になる
    • issue の閲覧、新しい issue の作成、タイトルと説明の編集、状態変更をサポート
    • すべての操作はローカルトランザクションストアにキューイングされ、接続が戻るとフラッシュされる
    • modulepreload は今必要なものを並列で取得し、ブラウザが直列の import chain で詰まらないようにする仕組み
    • サービスワーカーは次に必要になるものを準備する仕組み
    • Linear の高速ロード段階は、可能な限り多くのコード削除、小さな断片への分割、バックグラウンドのプリキャッシュであり、目標はネットワークリクエストを高速化するか完全に取り除くこと
  • Vendor バンドル構成

    • Linear が使用する各パッケージは個別のチャンクに分割され、独立してキャッシュされる
    • 従来の vendor.js は、依存関係を 1 つ更新しただけでも依存グラフ全体のキャッシュが無効化される
    • Linear のチャンク分割は単一の大きなファイルの代わりに細粒度の vendor キャッシュを実現し、特定の依存関係を更新した際にはそのチャンク 1 つだけが無効化され、残りはキャッシュに維持される
  • 大きなフォントファイルのロード

    • 不適切なフォントロードは、一時的に見えないテキスト、実際のフォント置き換え時のレイアウトシフト、preload の不一致による重複 fetch を引き起こす可能性がある
    • Linear は <head> で Inter Variable フォントを preload し、static.linear.app に preconnect している
    <link rel="preload"
          href="https://static.linear.app/fonts/InterVariable.woff2?v=4.1";
          as="font" type="font/woff2" crossorigin="anonymous">
    <link rel="preconnect" href="https://static.linear.app"; crossorigin>
    
    • Variable font は 100〜900 の全 weight 軸を単一の woff2 で処理し、weight ごとのリクエストをなくす
    • font-display: swap は fallback stack を即座にレンダリングし、Inter のロード完了後に置き換える
    • preload タグの crossorigin="anonymous" は、CSS が後で同じフォントを参照したときにブラウザがキャッシュ済みリソースを再利用できるようにする重要な設定
    • crossorigin がないと preload と CSS 参照の CORS モードが異なり、ブラウザがフォントを再取得してしまう問題が発生する
  • インラインのアプリシェル

    • Linear は <head> 内にローディング状態を描画するのに十分な CSS をインラインで入れ、外部スタイルシートのリクエストなしでアプリシェルを表示する
    • インライン JavaScript は初期体験に必要な分岐を即座に実行する
    • Electron と Linear user agent を検出して electron クラスを追加
    • localStorage.ApplicationStore がなければ logged-out クラスを追加
    • localStorage.splashScreenConfig からサイドバー背景、サイドバー幅、ダークモードのような shell token を復元
    • ユーザーがデスクトップアプリでリンクを開くよう設定している場合、サイドバー幅を 8px に調整
    • 最初の JavaScript バンドルがネットワークから到着する前に、ローディング画面はログイン状態に合わせてテーマ、サイズ、位置がすでに調整された状態になる
    • ユーザーが URL を入力して Enter を押した直後からアプリの準備ができているように感じさせる最速の方法は、初期 index.html レスポンスにアプリシェルを載せて返すこと
    広告
  • まずレンダリングし、認証は後で

    • 一般的な認証フローは、HTML fetch、バンドルロード、セッション検証、ユーザー fetch、ワークスペース fetch、レンダリングの順に進み、ユーザーが何かを見るまで 1〜3 秒かかることがある
    • Linear は認証も変更処理と同じように扱い、正常系を前提にバックグラウンドで検証する
    • 多くの CRUD アプリは実際のセッションを HttpOnly Cookie に保持し、フロントエンドが起動中にログイン状態を把握できるよう、JavaScript から読める別の Cookie や /me リクエストを追加する
    • Linear のインラインブートスクリプトは、並列の認証シグナルではなく localStorage.ApplicationStore の存在だけを確認する
    if (localStorage.getItem("ApplicationStore") === null) {
      document.documentElement.classList.add("logged-out");
    }
    
    • ApplicationStore があれば、ユーザーはこのブラウザで Linear を使ったことがあり、ワークスペースデータがすでに IndexedDB にある状態だとわかる
    • 値がなければレンダリングするデータがないため、shell は logged-out レイアウトに切り替わり、ログインフローへ進む
    • 実際のセッショントークンは Cookie にあり、バンドルはセッション状態を事前に判断しない
    • WebSocket handshake、sync delta、HTTP 呼び出しのいずれかが期限切れセッションで 401 を受け取った場合、クライアントはログインへリダイレクトする
    • 全体のパターンは、ローカルデータを信頼して即座にレンダリングし、サーバーを正確性の情報源として保持しつつ、両者を非同期に調整する構造

同期エンジン

  • Linearの速さは、サーバーをUIのsource of truthではなくsync targetとみなす決定から始まる
  • 速さは単一の要素ではなく、3つの軸がかみ合った結果
  • 1. データがすでにある

    • アプリ起動時にサーバーからワークスペースを取得するのではなく、IndexedDBからメモリ内のMobXオブジェクトプールへhydrateする
    • UIのすべてのクエリはまずオブジェクトプールに向かい、課題はすでにユーザーのデバイス上にあるため、“loading issues”状態がない
    • Linearは拡張の過程で、JavaScriptバンドルと似た原理で同期エンジンのデータをチャンク化した
    • 最も重い2つのテーブルであるIssueとCommentは一度に取得せず、必要なときにlazy-hydrateする
    • この方式はデータレベルのコード分割であり、起動コストがワークスペースのサイズではなくワークスペースの構造に従うようにする
    • 10,000件のIssueがあるワークスペースでも、100件のIssueがあるワークスペースとほぼ同じ速度で起動する
    • プロジェクトに入るとIssueはすでにあり、assigneeでフィルタリングしてもインデックスはすでに構築されている
  • 2. 変更はネットワークを待たない

    • Issueの状態を変えると、3つのことがほぼ同時に起こる
    • MobX observableの更新によってUIに変更を反映
    • IndexedDBの永続性のあるトランザクションキューに変更を記録
    • サーバー送信キューに変更を追加
    • この時点では、ネットワークはまだ使われていない
    • ユーザーは自分の変更を見るために待つ必要がなく、再試行、ロールバック、reload across durabilityはすべてバックグラウンドで処理される
    • サーバーが拒否した場合はobservableが元に戻り、短いflickerが発生するが、ほとんどの不正な変更はトランザクション作成前に捕捉される
    • Linearのフローはローカル変更から始まり、サーバーを許可段階ではなく確認段階として扱う
  • 3. 1つのデルタ、1つのセル

    • サーバーがユーザー自身の変更や他の人の変更を確認すると、何が移動したかを示す小さなJSON envelopeがクライアントに返ってくる
    • クライアントは、該当するMobX observableに値を書き込む形で変更を適用する
    • Linearのすべてのモデル属性はそれぞれobservableであり、その属性を読むすべてのコンポーネントはobserver()でラップされる
    • MobXは、どのコンポーネントがどのフィールドに依存しているかを正確に把握できる
    • 1つのIssueの1つのフィールド変更では、そのフィールドを読むコンポーネントだけが再レンダリングされ、親リストやサイドバー全体は再レンダリングされない
    • 50件のIssue更新は、リスト全体の再レンダリングではなく50個のセルの再レンダリングになる
    • 10人が同時に編集する忙しいワークスペースでも、更新受信コストは画面上の全項目ではなく、実際に変わった項目に応じて増加する
    広告
  • 3つがかみ合う理由

    • ローカルデータベースだけがあって楽観的書き込みがなければ、保存時には依然としてスピナーが発生する
    • 楽観的書き込みだけがあって細粒度のobservableがなければ、すべての更新で引っかかりが発生する
    • 細粒度のobservableだけがあってローカルデータベースがなければ、初期ロードでは依然として待機が発生する
    • Linearの速さは単一レイヤーの特性ではなく、システム全体の特性
    • バンドラーとローダーシェルは最初のpaintを速く感じさせ、同期エンジンは使い始めた後もその速さの感覚を維持する

速さのためのデザイン

  • 速さはエンジニアリングの問題であると同時にデザインの問題でもある
  • 最も速いアクション経路でもマウス、3つのメニュー、クリックが必要なら、ユーザーは内部エンジンの速度とは無関係にその手順のコストを支払う
  • Linearの速さのもう1つの軸は、キーボードをナビゲーションと作業完了の主要ツールとして統合した点
  • すべての一般的な作業にはshortcutがあり、command paletteは1回のキー入力で開き、right-click menuはカスタム構築されている
  • すべてのアクションにはshortcutがある

    • 単一文字はフォーカスされたIssueを編集し、2文字の組み合わせはナビゲーションに使われ、modifierはグローバル動作に使われる
    • Linearの初期段階からshortcutは基盤要素であり、同期エンジンにはどのアクションもいつでも実行できるよう設計された側面がある
    • UIの各所でshortcutが見え、最もよく使うshortcutは単一文字である
    • 初心者を排除しないため、すべてのアクションはマウスでも実行できる
  • Command paletteは常に1回のキー入力の距離にある

    • ⌘ kは、Linearのほぼすべてのアクションを検索できるcommand paletteを開く
    • 検索対象はIssue、プロジェクト、label、状態変更、ナビゲーション、Issue作成、設定、テーマ切り替えなど
    • command paletteはサーバーではなくローカルのMobXオブジェクトプールを検索するため、非常に速い
    • アプリ全体には単一のpaneからアクセスでき、ナビゲーション、Issue作成、状態変更はすべて検索で行える
    • command paletteは現在の作業コンテキストに合わせて適応し、各viewの中核アクションとshortcutを教える形で機能する
    • 速いアプリには優れたエンジニアリングとデザインの両方が必要であり、エンジニアリングの速さは単一の相互作用を速くし、デザインの速さは相互作用までの経路を短くする
    • 一日中使うツールでは、shortcutと2秒かかるマウス経路の違いがあらゆるアクションで積み重なる

アニメーション

  • 悪いアニメーションは、初期ロード、更新、データベースクエリ最適化で削減したミリ秒を最後の段階で再び浪費しかねない
  • 500msの height アニメーションのような要素は、ユーザーを待たせないようにする努力を台無しにしうる
  • アニメーションすべきプロパティはごくわずか

    • ブラウザの property change は、レンダリングパイプライン上の位置に応じて3層のコストを持つ
    • composited property である transformopacity は、処理を GPU に渡し、main thread とは独立して実行される
    • paint-triggering property である colorbackground-colorborder-colorfill は、layout をスキップするが pixel redraw を発生させる
    • layout-triggering property である widthheighttopleftmarginpadding は、その後のすべての要素の位置を再計算させるため、アニメーション対象にすべきではない
    広告
    /* Linear方式 */
    .row:hover {
      background-color: var(--color-bg-hover);
      transition: background-color 0.12s;
    }
    .icon-arrow {
      transform: translateX(0);
      transition: transform 0.15s;
    }
    
    • margin-left をアニメーションすると、hover された row の下にあるすべての row の layout が、transition 全体の 200ms にわたり毎フレーム再計算される
    • 長い issue 一覧では、この違いが滑らかな画面と jank を分ける要素になる
    • Linear のアニメーションプロパティはほとんどが transformopacity のような composited property であり、ときどき background-colorborder-color も使う
  • 節度が必要なときを知る

    • 毎日使うツールでは、マーケティングサイトでは見栄えのするアニメーションが作業の妨げになりうる
    • 位置を誤った小さな hover delay でさえ、ユーザーが気づくポイントになりうる
    • Linear の多くのアニメーションは origin を参照するため、動きが効果的に感じられる
    • status popover は status pill から scale out し、agent panel は toggle から slide in する
    • こうした motion は装飾的な fade ではなく、新しい要素がどこから来たのかを示す空間的な役割を果たす
  • 短く即時的な duration を保つ

    --speed-highlightFadeIn: 0s;
    --speed-highlightFadeOut: .15s;
    --speed-quickTransition: .1s;
    --speed-regularTransition: .25s;
    --speed-slowTransition: .35s;
    
    • 多くの design system は、デフォルトの duration を必要以上に長く設定している
    • Material の standard duration は 200ms で、iOS の spring は 350ms に近い
    • Linear のデフォルト値は、業界の慣行よりも短めの側にある
    • Linear は enter と exit に非対称の timing を使う
    • hover highlight、popover、agent panel は呼び出されると即座に表示され、閉じるときは 150ms かけて fade out する
    • agent window は即座に表示され、macOS に似た形で fade out する

Linearが速い理由

  • Linear のパフォーマンスは、単一の秘密や1つの技術ではなく、正しい何百もの意思決定が積み重なった結果だ
  • アプローチの多くはシンプルで、Next、TanStack、派手な framework がなくても、ユーザーに合ったアーキテクチャを早い段階で定めて維持した結果でもある
  • サーバーは UI の source of truth ではなく、sync target として動作する
  • データベースはブラウザ内にあり、変更はまずローカルに適用され、その後バックグラウンドで調整される
  • 初回ロードでは、より少ないコードをより多くの断片に分けて送信し、service worker はユーザーがログインページにいる間に残りを precache する
  • 認証はローカル状態に基づいて正常系を前提とし、後から検証する
  • 同期エンジンは IndexedDB から per-property MobX observable へ hydration するため、50件の issue 更新は一覧全体の再レンダリングではなく、50個のセルの再レンダリングとして処理される
  • 入力モデルは keyboard-first で、一般的な操作にはすべて shortcut とグローバル command palette がある
  • アニメーションは GPU フレンドリーなプロパティにとどまり、layout-triggering property はアニメーションしない
  • 難しいのは実装そのものよりも、コードベースが成熟し、拡張され、新たな制約に直面していく中で、何年にもわたり細部の品質に集中し続ける姿勢だ

1件のコメント

 
GN⁺ 4 시간 전
Hacker Newsのコメント
  • アプリケーションにこうした体験を組み込みたいなら、Zero(https://zero.rocicorp.dev/)を見るとよい
    ライブデモ: https://gigabugs.rocicorp.dev/
    代替の一覧もここにある: https://zero.rocicorp.dev/docs/when-to-use#alternatives
    内部動作が気になるなら、Replicacheの設計文書も参考になる: https://doc.replicache.dev/concepts/how-it-works
    ReplicacheはZeroの前身で、コアプロトコルは今でも同じ方式で動いている

    • Zeroは本当におすすめできる。抽象化が素晴らしく、細部まで丁寧に作られたソフトウェアだ
      データをクライアントに同期しておくことによる明確な性能上の利点だけでなく、Reactコードがどれほどシンプルになるかにも驚いた。同期エンジンがあるとクライアント側の状態の大半が消え、コンポーネントコードの大部分を同期的に考えられるようになる
    • しばらくZeroを使っている。最初はLinear式の同期エンジンを社内で作ろうとしていたが、Zeroを見つけた
      専任チームを組まずに得られるものとしては、おそらく最も近い選択肢だ
    • Zeroユーザーとして言うと、データベースが変わったらUIが数ミリ秒で即座に更新されるユーザー体験が欲しいときにぴったりのツールだ
  • Linearが速いという話はずっと聞いていたが、実際に毎日使ってみると熱は冷めた。検索はかなり遅く、UIはしばしば鈍重で見た目は良いが、「Pulse」は小規模でもノイズの洪水のようだ
    必要なものを見つけにくく、結局すべてをお気に入りに入れることになる。初期のTrelloはプロジェクト追跡体験として圧倒的に最高だった

    • 会議やハドルの最中にチケットを開こうとするとき、読み込みなのかキャッシュなのかわからない処理を不自然なほど長く待ちながら気まずく見守るのが本当に嫌だ
  • 昨年、誰かがLinear同期エンジンをリバースエンジニアリングしてGitHubに公開し、見事な解説も付けていた
    https://github.com/wzhudev/reverse-linear-sync-engine/blob/m...

  • こうしたローカルファースト同期Webアプリは本当に興味深く有用になり得るが、前提にはやや無理があると思う
    「Linearでイシューを更新するのに数ミリ秒で済む。従来のCRUDアプリでは同じ作業に約300msかかる」「クライアントとサーバーの間を行き来するすべてのデータは数百ミリ秒のコストを支払う」といった前提だ
    HTTPクライアントとサーバーの間の往復時間が光速の制約で大きくなる問題そのものは解決できないが、バックエンドをユーザーの近くに置いて高速化することはできる
    たとえば、ほとんどのユーザーに対して往復時間が約10ms以内の場所でWebアプリのバックエンドを運用し、バックエンド側も約10ms以内でレスポンスをレンダリングすることは十分可能だ。つまり従来のCRUDアプリでも同じ作業を300msではなく30ms程度にできる

    • 300msを速いと言うのを見て変だと思ったが、私の記憶ではTTFB 30msが長年の目標だった
      Linearはバックエンド側で正当な理由により時間がかかるためフロントエンドの助けが必要かもしれないが、それを一般化することはできない。JavaScriptの断片ひとつひとつにもそれ自体のコストがある
    • 「ほとんどのユーザーに対して往復時間約10ms以内でWebアプリのバックエンドを運用できる」というのは奇妙だ。us-east-1から10ms未満のAWSリージョンは実質的にus-east-2しかない: https://www.cloudping.co/
      us-west-1は60ms、eu-centra-1は100ms、アジアは200ms離れている。しかもこれはデータセンター間トラフィックの話で、実際の公衆インターネットで家庭向け回線まで含めた遅延はずっと悪い
      データベースは正確に1つのリージョンに置かなければならない。どこに置いても、地球上のユーザーの大多数はそこから100ms以上離れることになる
      エンドポイントがどこにあっても関係ないのは、エンドポイントがデータを読み書きするにはデータベースと通信しなければならないからだ。データをユーザーの近くに複製しようとした瞬間、結局はローカルファースト同期データベースを持つことになる
      自作するにせよ既製品を使うにせよ、その複製データベースはクライアント側同期と同じ問題をすべて抱えることになり、しかも相当なネットワーク遅延も依然として残る。物理法則は避けられないので、ほとんどのユーザーに0.25秒のコミットを与えるか、最終的整合性、つまり同期を選ぶしかない
    • ユーザー同士がかなり近い場所にいるか、あるいは悲しいがよくあるように、米国のユーザーだけ速ければよく他は気にしない場合にしか成り立たない
      もちろん、世界中のCDNエッジネットワークのような場所に「中間バックエンド」を置くことはできるが、その時点では「中間バックエンド」をクライアントに置くこの方式と同じ複雑性コストを払うことになる
    • クライアントに中間バックエンドを置き、まだ処理されていない変更をローカルストアに書き込み、バックグラウンドワーカーが必要なリトライ付きでバックエンドへ送るようにできる
      最悪の場合はバックグラウンドワーカーが更新失敗メッセージを発行し、UIスレッドがそれを受け取って表示すればよい。成功経路はそのまま稲妻のような速さを保てる
    • こうしたエッジバックエンドがすべて共有しなければならないデータベースがある場合でも可能なのか?
  • 最終的整合性データベースを書くのは難しく、Linearのユースケースでは問題ないかもしれないが、自分の更新がサーバー、つまりチームに届いたのか分からないのは問題だ
    以前参加した別のプロジェクトでは同期遅延が数え切れないほどの問題を生んだため、常に同期的な解決策を選んでいる。派手な機能は本当に必要なときだけ有効にし、むしろサーバーを徹底的に高速化して、ユーザーにはネットワーク遅延を受け入れてもらうほうがよい

    • Linearで不整合を経験したことはあるが、Jiraはゴミ捨て場なので仕方ない面もある
  • 会社でLinearを使っている。少数派なのは分かっているが、ユーザー体験が本当につらい。速いと呼ぶのも難しい
    ページ自体は技術的にはそれなりに速く読み込まれるが、半分くらいはデータ読み込みがまだ進行中だという視覚的表示もなく、ページ上の数字が変わるのを見せられる

    • Linearでよく起きる問題は、繰り返しの書き込みがときどき互いを上書きしてしまうことだ。入力して、少し考えようと止まり、さらに入力すると、Linearがデータを最初の入力時点の状態に戻してしまう
      ここまでひどいので、Linearでは一文だけの説明で課題を作り、詳しい内容はGitHubに行って埋めている。Linearがうまくやれて速いのはその程度だ
    • Linearは、自分がなくそうとしていた対象、つまり複雑なツールになってしまった
      残念だが、会社が生き残って上位市場に進むには、事実上その道しかない
    • Chromeを使っていないのでその影響かは分からないが、Linearのページはよく固まるか、初回読み込みに数秒かかる
      「速い」という言葉は使わない。そもそも読み込みに30秒かかるのに、課題更新が300msから「数」ミリ秒に減ることはあまり重要ではない
    • 前の職場では奇妙なUXのせいでLinearからJiraに切り替えた。意味の分かりにくいアイコンが多く、発見しづらく、ページの内容がなぜ変わるのかを示す表示もほとんどなかった
    • 本当にひどい。項目に期限日を追加する方法を同僚に聞かなければならなかったが、ナビゲーションパネルの中に隠れていた
      Jiraよりはましだが、それは基準がとても低いというだけだ
  • すごい。開発中のブラウザゲームとエンジンにも似たようなものを入れれば、初回読み込み後は読み込み状態を完全になくせるかもしれない。自分のものはサーバーなしの完全クライアントサイド静的アセット構成だ
    このゲームの性能にはこだわってきた。先週末までは、M1 MacBook Proで同時プレイヤー128人をシミュレーションしながら、経路探索、重い戦略ロジック、レンダリングをすべてビューポート内で処理し、120fpsを維持するのに苦労していて、ごくたまにフレームドロップがあり、フレーム時間は約4msだった
    週末の間にかなり強く性能改善に取り組み、今では同時プレイヤー2048人を1ms未満のフレーム時間でシミュレーションできる。レンダリングとすべてのロジック、手続き型生成まで含めた数値だ
    さらに11.2倍のCPUスロットリングをかけて低性能モバイル機器相当をシミュレートした場合でも、同時プレイヤー256〜512人で約5msのフレーム時間、安定した60fpsが出る。今の主なボトルネックは少しのロジック問題と、低性能端末で改善すべき起動・ブート時間で、Linearから学べる点がありそうだ

  • Linearは実際かなり遅いと感じた。しばらくタブを開きっぱなしにしていると、**CPU 100%**で回る週もあった

    • Firefoxではメモリもかなり使うようだ。Linearのタブを同時に何枚も開いておけない
  • 面白くはある。正直、Linearを「速い」と思ったことはない。大半のWebアプリと同じく遅延はあるが、JIRAと比べればもちろん光速だ
    Linear自体は素晴らしく、JIRAの拷問の後では本当に新鮮だ。楽観的ルートと「速さ」を語るなら、まずGmailから話すべきではないかと思う

  • 速度の答えはプリフェッチだ。基本的には初期化時点でクライアントデータベースをダウンロードし、キャッシュ無効化戦略を持たせる方式だ
    このパラダイムのデータ同期面を実現するためにstarfxを作った: https://starfx.bower.sh/learn#data-loading-strategy-stale-wh...