Tại sao Linear lại nhanh đến vậy? Phân tích kỹ thuật chi tiết

Công nghệ07 tháng 6, 2026·8 phút đọc

Linear nổi tiếng với tốc độ phản hồi gần như tức thì, vượt xa các ứng dụng web truyền thống. Bài viết này phân tích các kỹ thuật kỹ thuật đằng sau hiệu suất ấn tượng đó, từ việc sử dụng IndexedDB làm cơ sở dữ liệu cục bộ, tối ưu hóa bundle, cho đến kiến trúc đồng bộ hóa thông minh giúp ẩn đi độ trễ mạng.

Tại sao Linear lại nhanh đến vậy? Phân tích kỹ thuật chi tiết

Chỉ mất vài mili-giây để cập nhật một vấn đề (issue) trên Linear, trong khi một ứng dụng CRUD truyền thống làm điều tương tự mất khoảng 300ms. Họ đã làm được điều đó như thế nào? Không có viên đạn bạc nào cho hiệu suất cả. Thực tế là Linear được xây dựng từ nền tảng đúng đắn ngay từ đầu, sau đó được cải thiện bởi vô số quyết định tối ưu hóa.

Linear InterfaceLinear Interface

Mục tiêu của bài viết này là đi sâu vào các kỹ thuật giúp Linear mang lại cảm giác mượt mà đến vậy và giúp bạn áp dụng những kinh nghiệm này vào dự án của mình.

Cơ sở dữ liệu ngay trên trình duyệt

Hầu hết các ứng dụng web đều hoạt động trong một vòng lặp giống nhau: Người dùng nhấp chuột -> Trình duyệt gửi yêu cầu HTTP -> Máy chủ truy vấn cơ sở dữ liệu và trả về -> Trình duyệt vẽ lại giao diện. Kết quả cuối cùng là một vòng xoay tải (spinner), khung xương (skeleton) hoặc giao diện bị đóng băng trong vài trăm mili-giây trong khi ứng dụng chờ mạng.

Linear đã đảo ngược mối quan hệ truyền thống này. Cơ sở dữ liệu thực tế mà giao diện người dùng (UI) đọc từ đó nằm ngay trên trình duyệt, cụ thể là trong IndexedDB. Các thay đổi (mutations) được áp dụng tại chỗ trước tiên, sau đó mới đẩy không đồng bộ lên máy chủ, nơi máy chủ sẽ phát các thay đổi (deltas) trở lại các máy khách khác thông qua WebSocket.

Đây được coi là mảnh ghép quan trọng nhất cho hiệu suất của Linear. Khi mục tiêu của bạn là xây dựng một ứng dụng web nhanh, nút thắt lớn nhất bạn phải đối mặt là mạng. Bất kỳ dữ liệu nào được gửi giữa máy khách và máy chủ đều tốn hàng trăm mili-giây. Cách tiếp cận tốt nhất là loại bỏ hoàn toàn nhu cầu gửi yêu cầu mạng: đó chính xác là những gì Linear làm.

// Ứng dụng web truyền thống cập nhật máy chủ
async function updateIssue({ issue }) {
  showSpinner();
  const response = await fetch(`/api/issues/${issue.id}`, {
    method: "PATCH",
    body: JSON.stringify({ title: issue.title }),
  });
  const updated = await response.json();
  setIssue(updated)
  hideSpinner();
}

// So với Linear
issue.title = "Khởi động ứng dụng nhanh hơn";
issue.save();

Dòng đầu tiên, issue.title = ..., cập nhật kho dữ liệu trong bộ nhớ (MobX observable trong trường hợp của Linear). Dòng thứ hai, issue.save(), xếp hàng một giao dịch mà động cơ đồng bộ (sync engine) của họ đóng gói và đẩy lên máy chủ. Chìa khóa ở đây là UI được kết xuất lại đồng bộ dựa trên bản cập nhật cục bộ trong bộ nhớ. Không có vòng xoay tải vì không có gì cần chờ đợi, dữ liệu được đồng bộ hóa trong nền.

Tối ưu hóa lần tải đầu tiên

Một điều mà Linear cực kỳ chú trọng là thời gian tải lần đầu. Đối với các công cụ năng suất, thời gian trước khi bạn thực sự có thể bắt đầu làm việc là một trong những chi tiết quan trọng nhất.

Bước đầu tiên để làm cho ứng dụng cảm thấy tức thì xảy ra từ lâu trước khi chạy. Nó bắt đầu ở thời điểm xây dựng (build time). Linear đã viết lại quy trình xây dựng của họ bốn lần: Parcel -> Rollup -> Vite -> Rolldown. Mỗi lần di chuyển đều được thúc đẩy bởi cùng một mục tiêu: giảm lượng JavaScript và CSS và cải thiện trải nghiệm của nhà phát triển.

Họ tuyên bố đã giảm 50% lượng mã được gửi, giảm 30% kích thước sau khi nén, và thời gian hiển thị đầu tiên (time-to-first-paint) giảm 59% trên Safari. Phần lớn thành công này đến từ việc chỉ nhắm đến các trình duyệt hiện đại, loại bỏ mã chết (dead-code elimination) tốt hơn và chia nhỏ mã (code splitting) một cách tích cực.

Preloading chunksPreloading chunks

Tải trước (Preloading) và Service Worker

Sau khi chia nhỏ JavaScript thành các mảnh nhỏ nhất có thể, Linear bắt đầu thực hiện công việc trong nền. Trước khi bất kỳ JavaScript nào chạy, trình duyệt đã nhìn thấy toàn bộ danh sách các mảnh và kích hoạt các yêu cầu song song. Đến lúc tập lệnh nhập đến lần nhập đầu tiên, các mảnh đó đã nằm trong bộ nhớ đệm.

Service Worker của Linear lưu vào bộ nhớ đệm các phần còn lại của ứng dụng (các mảnh theo tuyến đường mà người dùng chưa truy cập) trong nền. Worker này có danh sách precache khoảng 1.200 tài sản (assets) bao gồm các mảnh tuyến đường, biểu tượng và phông chữ. Trong vài giây sau khi truy cập màn hình đăng nhập, toàn bộ ứng dụng đã nằm trong bộ nhớ đệm.

Xác thực: Hiển thị trước, kiểm tra sau

Xác thực là một bước khác mà hầu hết các ứng dụng thường đánh đổi hiệu suất. Quy trình thông thường: tải HTML, tải bundle, xác thực phiên, tải người dùng, tải không gian làm việc, sau đó mới kết xuất. Linear xử lý xác thực giống như cách họ xử lý các thay đổi: giả định kịch bản tích cực và xác minh trong nền.

Thay vì duy trì một tín hiệu xác thực song song, tập lệnh khởi động (boot script) được nhúng chỉ cần kiểm tra xem localStorage.ApplicationStore có tồn tại hay không. Nếu có, người dùng đã sử dụng Linear trong trình duyệt này trước đây, nghĩa là không gian làm việc của họ đã nằm trong IndexedDB. Nếu thiếu, không có gì để hiển thị cả, nên giao diện chuyển sang bố cục đã đăng xuất.

Động cơ đồng bộ (Sync Engine)

Hầu hết những gì làm cho Linear nhanh nằm ở một quyết định: máy chủ là mục tiêu đồng bộ, không phải nguồn sự thật cho UI. Có ba trụ cột chính tạo nên tốc độ này:

  1. Dữ liệu đã có sẵn ở đó: Khi ứng dụng khởi động, nó không lấy không gian làm việc từ máy chủ. Nó hydrate (tải dữ liệu) từ IndexedDB vào một nhóm đối tượng MobX trong bộ nhớ. Mọi truy vấn từ UI đều đi đến nhóm này trước.
  2. Các thay đổi không chờ mạng: Khi bạn thay đổi trạng thái của một vấn đề, ba điều xảy ra gần như cùng một lúc: MobX observable cập nhật để UI phản ánh thay đổi, thay đổi được ghi vào hàng đợi giao dịch bền vững trong IndexedDB, và nó được xếp hàng cho máy chủ. Người dùng không bao giờ chờ đợi để thấy thay đổi của chính mình.
  3. Một delta, một ô: Khi máy chủ xác nhận một thay đổi, dữ liệu trả về dưới dạng một bao bọc JSON nhỏ mô tả những gì đã di chuyển. Vì mọi thuộc tính trên mọi mô hình trong Linear đều là observable riêng biệt, MobX biết chính xác những thành phần nào phụ thuộc vào trường nào. Một thay đổi cập nhật một trường của một vấn đề sẽ chỉ kết xuất lại chính xác các thành phần đọc trường đó.

IndexedDB ConceptIndexedDB Concept

Thiết kế cho tốc độ

Tốc độ không chỉ là vấn đề kỹ thuật, mà còn là vấn đề thiết kế. Một động cơ đồng bộ hoàn hảo vẫn sẽ thua nếu mô hình đầu vào chậm chạp. Một gócstone khác của tốc độ Linear là cách họ tích hợp bàn phím như một công cụ chính để điều hướng và hoàn thành công việc.

Mọi hành động phổ biến đều có phím tắt. Bảng lệnh (command palette) chỉ cách một lần nhấn phím. Bảng lệnh này cực kỳ nhanh vì nó tìm kiếm trong nhóm đối tượng MobX cục bộ, không phải trên máy chủ. Kiến trúc này cho phép toàn bộ ứng dụng có thể truy cập được từ một ngăn duy nhất.

Hiệu ứng chuyển động (Animations)

Tất cả công việc nỗ lực trên vẫn có thể bị phá hỏng bởi các hiệu ứng chuyển động tồi. Trình duyệt có ba cấp độ thay đổi thuộc tính, và chi phí tăng lên theo mức độ cao của nó trong quy trình kết xuất.

Linear chỉ giới hạn hoạt ảnh ở một số ít thuộc tính, chủ yếu là các thuộc tính kết hợp (composite properties) như transformopacity, và đôi khi là các thuộc tính như background-color. Họ không bao giờ hoạt ảnh các thuộc tính kích hoạt bố cục (layout-triggering properties) như width, height, margin, vì điều này buộc trình duyệt phải tính toán lại vị trí của mọi phần tử tiếp theo trên trang.

Họ cũng biết khi nào cần giữ lại. Trong một công cụ được sử dụng hàng ngày, các hoạt ảnh bạn thích trên trang web tiếp thị bắt đầu trở nên cản trở. Linear sử dụng thời lượng chuyển động ngắn hơn nhiều so với tiêu chuẩn ngành (thường dưới 100ms) và thời gian không đối xứng cho việc nhập và thoát.

Kết luận

Không có một yếu tố đơn nào làm cho một ứng dụng có hiệu suất cao. Đó là sự kết hợp của hàng trăm quyết định được đưa ra đúng đắn. Máy chủ là mục tiêu đồng bộ. Cơ sở dữ liệu nằm trong trình duyệt. Các thay đổi được áp dụng cục bộ trước và hòa giải trong nền. Lần tải đầu tiên gửi ít mã hơn ở nhiều mảnh hơn. Mô hình đầu vào ưu tiên bàn phím trước.

Bài học lớn nhất là sự cống hiến cho nghề nghiệp trong nhiều năm, khi cơ sở mã trưởng thành, mở rộng và đối mặt với các ràng buộc mới. Nếu bạn chưa từng thử Linear, tôi khuyên bạn nên trải nghiệm để thấy tất cả những điều này hoạt động trong thực tế.

Chia sẻ:FacebookX
Nội dung tổng hợp bằng AI, mang tính tham khảo. Xem bài gốc ↗