- Linear là công cụ năng suất xử lý tác vụ quản lý issue bằng cơ sở dữ liệu trong trình duyệt và đồng bộ ưu tiên cục bộ, giúp các cập nhật issue được phản ánh lên UI chỉ trong vài mili giây
- Cơ sở dữ liệu thực tế mà UI đọc nằm trong IndexedDB; thay đổi được áp dụng trước ở máy cục bộ, sau đó gửi bất đồng bộ lên máy chủ và được phân phối lại dưới dạng delta qua WebSocket
- Lần tải đầu sử dụng chiến lược tải giảm thời gian chờ mạng với lượng JavaScript·CSS truyền xuống ít, chia nhỏ mã quyết liệt, modulepreload, service worker pre-cache và app shell nội tuyến
- Bộ máy đồng bộ hóa hydrate dữ liệu IndexedDB thành pool đối tượng MobX, lưu thay đổi vào hàng đợi transaction và chỉ render lại các ô cần thiết bằng trạng thái observable ở cấp trường
- Cảm giác nhanh vượt trội là kết quả của thiết kế hệ thống kết hợp cả nhập liệu ưu tiên bàn phím, bảng lệnh toàn cục, animation thân thiện với GPU và thời gian chuyển cảnh ngắn
Cơ sở dữ liệu bên trong trình duyệt
- Ứng dụng web CRUD truyền thống sau cú nhấp của người dùng sẽ đi qua yêu cầu HTTP, truy vấn cơ sở dữ liệu ở máy chủ, nhận phản hồi rồi repaint trình duyệt, khiến spinner·skeleton·UI bị khựng xuất hiện trong hàng trăm mili giây
- Linear đặt cơ sở dữ liệu thực tế mà UI đọc trong IndexedDB của trình duyệt; thay đổi được áp dụng trước ở cục bộ rồi gửi bất đồng bộ lên máy chủ, còn máy chủ phát delta tới các client khác qua WebSocket
- Nút thắt lớn nhất của web app nhanh là mạng, và việc truyền dữ liệu giữa client với server tạo ra chi phí hàng trăm mili giây
- Luồng cốt lõi của Linear là làm cho các yêu cầu mạng trở nên vô hình với người dùng và loại bỏ trạng thái tải bất cứ khi nào có thể
// Linear
issue.title = "Faster app launch";
issue.save();
issue.title = "Faster app launch" cập nhật kho dữ liệu trong bộ nhớ, và trong trường hợp của Linear là dùng MobX observable
issue.save(); là thao tác bộ máy đồng bộ hóa xử lý theo lô và đưa transaction vào hàng đợi để flush lên máy chủ
- UI được render lại đồng bộ dựa trên thay đổi bộ nhớ cục bộ, còn việc đồng bộ dữ liệu diễn ra trong nền nên không cần spinner
- Tuomas từng nói tại một hội nghị năm 2024 rằng đoạn mã đầu tiên anh viết ở Linear là bộ máy đồng bộ hóa, và mô tả đây là cách tiếp cận không phổ biến ở startup
- Phần lớn ứng dụng không cần tự xây bộ máy đồng bộ hóa như Linear; chỉ với cập nhật lạc quan của TanStack Query và SWR cũng có thể đạt cảm giác tốc độ khá gần
- Yêu cầu lạc quan mang lại hiệu quả cải thiện lớn nhờ loại bỏ spinner không cần thiết, cập nhật trạng thái tức thì, xác minh trong nền và rollback khi cần
- Độ phản hồi của UI không nên phụ thuộc vào độ trễ mạng, và tốc độ mà người dùng cảm nhận được được quyết định bởi độ phản hồi của giao diện hơn là tốc độ phản hồi của máy chủ
-
Nhìn lướt qua stack của Linear
- Linear được xây dựng trên một stack đơn giản gồm React, TypeScript, MobX, Postgres và CDN
- Frontend dùng React và
react-dom, MobX, TypeScript, Rolldown-Vite cùng plugin-react-oxc, ProseMirror và y-prosemirror, Radix UI primitives, Emotion và StyleX, Comlink, idb, graphql-request, Sentry, Inter Variable
- Backend dùng Node.js và TypeScript, PostgreSQL trên Cloud SQL, Memorystore Redis, turbopuffer, Kubernetes của GCP và Cloudflare Workers
- Client desktop dựa trên Electron, còn mobile được viết lại hoàn chỉnh riêng bằng Swift cho iOS và Kotlin
- Website marketing dùng Next.js, styled-components và inline SVG sprite
- Linear vẫn duy trì client-side rendering, là ví dụ cho thấy nếu có kiến trúc và thiết kế đúng thì CSR cũng có thể cho cảm giác tức thì
- Giữ toàn bộ ứng dụng ở phía client giúp có được mô hình tư duy đơn giản hơn, giảm các độ phức tạp như phân tách server·client, khả năng truy cập
window, hay thiết lập cache header
Khiến lần tải đầu tiên có cảm giác tức thì
- Trong các công cụ năng suất, thời gian từ lúc mở ứng dụng đến khi thực sự bắt đầu làm việc là một chi tiết quan trọng
- Tải ban đầu của ứng dụng phía client có thể chậm do diễn ra theo thứ tự: yêu cầu
index.html, yêu cầu JavaScript và CSS, xử lý xác thực, rồi yêu cầu API để hiển thị ứng dụng
-
Luồng bundler của Linear: Parcel, Rollup, Vite, Rolldown
- Cảm giác nhanh tức thì bắt đầu từ build time, trước cả runtime; để tải nhanh, điều quan trọng là giảm lượng JavaScript và CSS phải truyền đi
- Linear đã viết lại pipeline build theo thứ tự Parcel → Rollup → Vite → Rolldown, và mỗi lần chuyển đổi đều nhắm tới việc giảm lượng JavaScript·CSS và cải thiện trải nghiệm lập trình viên
- Các chỉ số cải thiện theo blog của Linear
- Giảm 50% lượng mã được truyền tải
- Giảm 30% kích thước sau nén
- Cải thiện 10~30% thời gian tải trang với cold cache
- Giảm 59% Time-to-first-paint của chế độ xem active-issues trên Safari
- Giảm 70~80% mức sử dụng bộ nhớ
- Phần lớn cải thiện đến từ sự kết hợp giữa quyết định chỉ nhắm tới các trình duyệt hiện đại, dead-code elimination tốt hơn và chia nhỏ mã một cách mạnh tay
- Việc ngừng hỗ trợ legacy mang lại lợi ích lớn, dẫn tới loại bỏ polyfill, transpile ES5 và fallback
nomodule
- Ngay cả sau tối ưu hóa, Linear vẫn truyền khoảng 21MB JavaScript đã minify, nhưng chia rất mạnh thành hàng trăm chunk ở cấp route để chỉ tải khi cần
- Điểm cốt lõi không phải là chọn bundler nào, mà là loại bỏ trình duyệt cũ, chuyển sang ESM gốc và chia nhỏ mã tích cực
- Những bước này cộng dồn lại giúp JavaScript của lần tải đầu tiên của Linear giảm xuống còn khoảng một nửa, còn thời gian build giảm không chỉ một vài chữ số mà giảm cả một bậc độ lớn
-
Preload sau lần tải ban đầu
- Khi chia JavaScript thành các chunk nhỏ, sẽ phát sinh vấn đề tải dạng thác nước khi mỗi chunk lại import các chunk khác
- Linear cấu hình để trước khi JavaScript chạy, trình duyệt đã nhìn thấy toàn bộ danh sách và bắt đầu các yêu cầu song song, nhờ đó đến khi entry script chạm tới
import đầu tiên thì các chunk liên quan đã nằm sẵn trong cache
- Bằng cách khớp
modulepreload trong <head> với giá trị crossorigin của entry script, trình duyệt không coi preload và import là hai tài nguyên tách biệt mà tái sử dụng fetch đã được cache
- Timeline tải nguội chuyển từ thác nước tuần tự sang một đợt song song duy nhất; công việc mạng vẫn tồn tại nhưng được thực hiện cùng lúc
- Công việc này được thực hiện ngầm khi người dùng vừa tới trang đăng nhập lần đầu, và vài giây sau toàn bộ ứng dụng đã được đưa vào cache để có thể phục vụ tức thì
-
Service worker cho tốc độ nhanh hơn và khả năng offline
- Các chunk ở cấp route của những view người dùng chưa truy cập sẽ được service worker cache ở nền
- Service worker có precache manifest được nhúng trong mã nguồn, bao phủ khoảng 1.200 asset đã hash gồm route chunk, icon và font
- Cấu trúc này giúp toàn bộ ứng dụng vào cache chỉ trong vài giây sau khi đến màn hình đăng nhập
- Những lần điều hướng sau đó bỏ qua mạng hoàn toàn, service worker phản hồi trực tiếp từ cache riêng mà không đi qua HTTP cache
- Khi kết hợp với engine đồng bộ ưu tiên cục bộ và dữ liệu người dùng đã được lưu trong IndexedDB, Linear có thể dùng cả khi offline
- Hỗ trợ đọc issue, tạo issue mới, chỉnh sửa tiêu đề và mô tả, thay đổi trạng thái
- Mọi thao tác đều được xếp hàng trong kho giao dịch cục bộ và sẽ được flush khi kết nối quay lại
modulepreload là cơ chế lấy song song những gì cần ngay bây giờ để trình duyệt không bị chặn bởi chuỗi import tuần tự
- Service worker là cơ chế chuẩn bị những gì sẽ cần tiếp theo
- Các bước tải nhanh của Linear là loại bỏ nhiều mã nhất có thể, chia thành các mảnh nhỏ và precache ở nền; mục tiêu là làm cho các yêu cầu mạng nhanh hơn hoặc loại bỏ chúng hoàn toàn
-
Cấu trúc vendor bundle
- Mỗi package Linear sử dụng được tách thành một chunk riêng và được cache độc lập
vendor.js truyền thống sẽ làm vô hiệu cache của toàn bộ đồ thị phụ thuộc chỉ vì cập nhật một dependency
- Cách chia chunk của Linear tạo ra cơ chế cache vendor chi tiết thay vì một file lớn duy nhất; khi một dependency cụ thể được cập nhật, chỉ chunk đó bị vô hiệu còn phần còn lại vẫn giữ trong cache
-
Tải các file font lớn
- Tải font sai cách có thể khiến văn bản tạm thời không hiển thị, gây layout shift khi đổi sang font thật và tạo fetch trùng lặp do preload không khớp
- Linear preload font Inter Variable trong
<head> và preconnect tới static.linear.app
<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 xử lý toàn bộ trục weight từ 100~900 bằng một woff2 duy nhất, loại bỏ các yêu cầu theo từng weight
font-display: swap hiển thị ngay fallback stack rồi thay bằng Inter sau khi tải xong
crossorigin="anonymous" trong thẻ preload là thiết lập then chốt để khi CSS tham chiếu cùng font đó về sau, trình duyệt có thể tái sử dụng tài nguyên đã cache
- Nếu không có
crossorigin, chế độ CORS của preload và tham chiếu từ CSS sẽ khác nhau, khiến trình duyệt phải tải lại font
-
App shell inline
- Linear đặt inline trong
<head> phần CSS đủ để vẽ trạng thái loading, nhờ đó có thể hiển thị app shell mà không cần yêu cầu stylesheet bên ngoài
- JavaScript inline thực hiện ngay các nhánh cần thiết cho trải nghiệm ban đầu
- Phát hiện Electron và user agent của Linear để thêm class
electron
- Nếu không có
localStorage.ApplicationStore thì thêm class logged-out
- Khôi phục các shell token như nền sidebar, độ rộng sidebar, dark mode từ
localStorage.splashScreenConfig
- Nếu người dùng thiết lập mở liên kết bằng ứng dụng desktop thì điều chỉnh độ rộng sidebar thành
8px
- Trước khi bundle JavaScript đầu tiên đến từ mạng, màn hình loading đã có theme, kích thước và vị trí khớp với trạng thái đăng nhập
- Cách nhanh nhất để khiến người dùng cảm thấy ứng dụng đã sẵn sàng ngay khi nhấn Enter sau khi nhập URL là gửi app shell ngay trong phản hồi
index.html ban đầu
-
Render trước, xác thực sau
- Luồng xác thực thông thường diễn ra theo thứ tự fetch HTML, tải bundle, kiểm tra session, fetch người dùng, fetch workspace rồi render, nên người dùng có thể phải chờ 1~3 giây mới thấy được thứ gì đó
- Linear xử lý xác thực giống như xử lý thay đổi: giả định đường đi bình thường và xác minh trong nền
- Phần lớn ứng dụng CRUD giữ session thực trong cookie HttpOnly, rồi thêm một cookie riêng mà JavaScript đọc được hoặc một yêu cầu
/me để frontend biết trạng thái đăng nhập trong lúc khởi động
- Inline boot script của Linear không dùng tín hiệu xác thực song song mà chỉ kiểm tra sự tồn tại của
localStorage.ApplicationStore
if (localStorage.getItem("ApplicationStore") === null) {
document.documentElement.classList.add("logged-out");
}
- Nếu có
ApplicationStore, điều đó nghĩa là người dùng đã từng dùng Linear trên trình duyệt này và dữ liệu workspace đã có sẵn trong IndexedDB
- Nếu không có giá trị đó thì không có dữ liệu để render, nên shell chuyển sang layout logged-out và tiếp tục luồng đăng nhập
- Session token thực nằm trong cookie, và bundle không phán đoán trước trạng thái session
- Nếu một trong các bước như WebSocket handshake, sync delta hoặc gọi HTTP nhận 401 do session hết hạn, client sẽ chuyển hướng tới đăng nhập
- Toàn bộ mẫu này là tin cậy dữ liệu cục bộ để render ngay lập tức, coi server là nguồn chính xác và điều phối cả hai theo cách bất đồng bộ
Công cụ đồng bộ
- Tốc độ của Linear bắt đầu từ quyết định xem server không phải là source of truth của UI mà là mục tiêu đồng bộ
- Tốc độ không đến từ một yếu tố đơn lẻ mà là kết quả của ba trục gắn kết với nhau
-
1. Dữ liệu đã có sẵn
- Khi khởi động ứng dụng, không lấy workspace từ server mà hydrate từ IndexedDB vào object pool MobX trong bộ nhớ
- Mọi truy vấn của UI đều hướng đến object pool trước, và vì issue đã có sẵn trên thiết bị của người dùng nên không có trạng thái “đang tải issue”
- Trong quá trình mở rộng, Linear chia dữ liệu của công cụ đồng bộ thành các chunk theo nguyên lý tương tự JavaScript bundle
- Hai bảng nặng nhất là Issue và Comment không được tải cùng lúc mà lazy-hydrate khi cần
- Cách này là code splitting ở cấp dữ liệu, khiến chi phí khởi động đi theo cấu trúc workspace thay vì kích thước workspace
- Một workspace có 10.000 issue vẫn khởi động gần như nhanh tương đương workspace có 100 issue
- Khi vào project thì issue đã có sẵn, và khi lọc theo assignee thì chỉ mục cũng đã được xây dựng sẵn
-
2. Thay đổi không chờ mạng
- Khi đổi trạng thái issue, ba việc xảy ra gần như đồng thời
- Cập nhật MobX observable để phản ánh thay đổi trên UI
- Ghi lại thay đổi vào hàng đợi transaction bền vững của IndexedDB
- Thêm thay đổi vào hàng đợi gửi lên server
- Ở thời điểm này mạng vẫn chưa được sử dụng
- Người dùng không phải chờ để thấy thay đổi của chính mình, còn retry, rollback và reload across durability đều được xử lý ở chế độ nền
- Nếu server từ chối thì observable sẽ bị hoàn tác và xuất hiện hiện tượng nhấp nháy ngắn, nhưng phần lớn thay đổi sai đều bị chặn trước khi transaction được tạo
- Luồng của Linear bắt đầu từ thay đổi cục bộ và xem server không phải là bước cấp phép mà là bước xác nhận
-
3. Một delta, một ô
- Khi server xác nhận thay đổi của người dùng hoặc thay đổi từ người khác, một JSON envelope nhỏ mô tả nội dung đã dịch chuyển sẽ được trả về client
- Client áp dụng thay đổi bằng cách ghi giá trị vào MobX observable tương ứng
- Mọi thuộc tính model trong Linear đều là observable riêng biệt, và mọi component đọc thuộc tính đó đều được bọc bằng
observer()
- MobX có thể biết chính xác component nào phụ thuộc vào trường nào
- Thay đổi một trường của một issue chỉ khiến các component đọc trường đó render lại, thay vì render lại danh sách cha hoặc toàn bộ sidebar
- Cập nhật 50 issue đồng nghĩa render lại 50 ô, chứ không phải render lại toàn bộ danh sách
- Ngay cả trong một workspace bận rộn với 10 người cùng chỉnh sửa, chi phí nhận cập nhật cũng tăng theo các mục thực sự thay đổi chứ không theo toàn bộ mục đang có trên màn hình
-
Vì sao ba yếu tố này gắn kết với nhau
- Nếu chỉ có cơ sở dữ liệu cục bộ mà không có optimistic write thì khi lưu vẫn xuất hiện spinner
- Nếu chỉ có optimistic write mà không có observable chi tiết thì mọi cập nhật vẫn gây khựng
- Nếu chỉ có observable chi tiết mà không có cơ sở dữ liệu cục bộ thì lần tải đầu vẫn phải chờ
- Tốc độ của Linear không phải thuộc tính của một lớp đơn lẻ mà là thuộc tính của toàn bộ hệ thống
- Bundler và loader shell khiến first paint có cảm giác nhanh, còn công cụ đồng bộ duy trì cảm giác nhanh đó sau khi bắt đầu sử dụng
Thiết kế vì tốc độ
- Tốc độ vừa là vấn đề kỹ thuật vừa là vấn đề thiết kế
- Nếu con đường thao tác nhanh nhất vẫn đòi hỏi chuột, ba lớp menu và một cú nhấp, thì người dùng vẫn phải trả chi phí cho các bước đó bất kể động cơ bên trong nhanh đến đâu
- Một trục tốc độ khác của Linear là tích hợp bàn phím làm công cụ chính cho điều hướng và hoàn thành công việc
- Mọi tác vụ phổ biến đều có shortcut, command palette mở bằng một lần nhấn phím, và right-click menu được xây dựng tùy biến
-
Mọi hành động đều có shortcut
- Một ký tự đơn dùng để chỉnh sửa issue đang được focus, tổ hợp hai ký tự dùng cho điều hướng, còn modifier dùng cho hành động toàn cục
- Ngay từ giai đoạn đầu của Linear, shortcut đã là thành phần nền tảng, và công cụ đồng bộ cũng được thiết kế để bất kỳ hành động nào cũng có thể được thực hiện bất cứ lúc nào
- Shortcut xuất hiện khắp UI, và những shortcut được dùng thường xuyên nhất là ký tự đơn
- Để không loại trừ người mới, mọi hành động vẫn có thể thực hiện bằng chuột
-
Command palette luôn chỉ cách một lần nhấn phím
⌘ k mở command palette để tìm kiếm gần như mọi hành động trong Linear
- Có thể tìm issue, project, label, thay đổi trạng thái, điều hướng, tạo issue, cài đặt, chuyển theme và hơn thế nữa
- Command palette tìm kiếm trên object pool MobX cục bộ chứ không phải server nên rất nhanh
- Toàn bộ ứng dụng có thể được truy cập trong một pane duy nhất, và điều hướng, tạo issue, thay đổi trạng thái đều được thực hiện qua tìm kiếm
- Command palette thích ứng theo ngữ cảnh công việc hiện tại và hoạt động như một cách dạy các hành động cốt lõi cùng shortcut của từng view
- Một ứng dụng nhanh cần cả kỹ thuật lẫn thiết kế xuất sắc; tốc độ kỹ thuật làm cho từng tương tác diễn ra nhanh, còn tốc độ thiết kế rút ngắn con đường dẫn tới tương tác đó
- Trong một công cụ được dùng suốt cả ngày, khác biệt giữa shortcut và một đường chuột mất 2 giây sẽ cộng dồn trên mọi hành động
Hoạt ảnh
- Hoạt ảnh tệ có thể làm lãng phí lại ở bước cuối những mili giây đã tiết kiệm được nhờ tối ưu tải ban đầu, cập nhật và truy vấn cơ sở dữ liệu
- Những yếu tố như hoạt ảnh
height kéo dài 500ms có thể phá hỏng nỗ lực giúp người dùng không phải chờ đợi
-
Chỉ có một vài thuộc tính nên được dùng để làm hoạt ảnh
- Việc thay đổi property trong trình duyệt có ba tầng chi phí tùy theo vị trí của nó trong pipeline render
- Các composited property như
transform và opacity chuyển công việc sang GPU và chạy độc lập với main thread
- Các paint-triggering property như
color, background-color, border-color, fill bỏ qua layout nhưng vẫn gây redraw pixel
- Các layout-triggering property như
width, height, top, left, margin, padding buộc phải tính lại vị trí của mọi phần tử phía sau và không nên là đối tượng của hoạt ảnh
/* Cách Linear làm */
.row:hover {
background-color: var(--color-bg-hover);
transition: background-color 0.12s;
}
.icon-arrow {
transform: translateX(0);
transition: transform 0.15s;
}
- Nếu animate
margin-left, layout của mọi row nằm dưới row đang hover sẽ bị tính lại ở mỗi frame trong suốt toàn bộ 200ms transition
- Trong danh sách issue dài, khác biệt này là ranh giới giữa hiển thị mượt mà và giật khựng
- Các thuộc tính hoạt ảnh của Linear chủ yếu là composited property như
transform và opacity, đôi khi dùng background-color và border-color
-
Cần biết lúc nào nên tiết chế
- Với công cụ dùng hằng ngày, những hoạt ảnh đẹp mắt trên trang marketing có thể làm cản trở công việc
- Chỉ một độ trễ hover nhỏ đặt sai chỗ cũng có thể trở thành điểm người dùng nhận ra ngay
- Nhiều hoạt ảnh của Linear hiệu quả vì chúng tham chiếu tới origin
- Popover trạng thái scale out từ status pill, còn panel agent slide in từ toggle
- Kiểu chuyển động này không phải fade trang trí mà đóng vai trò không gian, cho biết phần tử mới đến từ đâu
-
Giữ duration ngắn và tức thời
--speed-highlightFadeIn: 0s;
--speed-highlightFadeOut: .15s;
--speed-quickTransition: .1s;
--speed-regularTransition: .25s;
--speed-slowTransition: .35s;
- Nhiều design system đặt duration mặc định dài hơn mức cần thiết
- Standard duration của Material là 200ms, còn spring của iOS gần 350ms
- Giá trị mặc định của Linear nằm ở phía ngắn hơn so với thông lệ ngành
- Linear dùng timing bất đối xứng cho enter và exit
- Hover highlight, popover và panel agent xuất hiện ngay khi được gọi, rồi fade out trong 150ms khi đóng lại
- Cửa sổ agent xuất hiện ngay lập tức và fade out tương tự macOS
Cách Linear đạt được tốc độ
- Hiệu năng của Linear không đến từ một bí quyết hay một công nghệ duy nhất, mà là kết quả tích lũy của hàng trăm quyết định đúng đắn
- Phần lớn cách tiếp cận này khá đơn giản, và là kết quả của việc sớm chọn rồi kiên trì duy trì một kiến trúc phù hợp với người dùng, không cần Next, TanStack hay framework hào nhoáng
- Server không hoạt động như source of truth của UI mà như một sync target
- Cơ sở dữ liệu nằm trong trình duyệt, và thay đổi được áp dụng trước ở local rồi điều phối lại trong nền
- Lần tải đầu gửi ít code hơn nhưng chia thành nhiều mảnh hơn, và service worker sẽ precache phần còn lại trong khi người dùng đang ở trang đăng nhập
- Xác thực giả định happy path dựa trên trạng thái local rồi xác minh sau
- Sync engine hydrate từ IndexedDB thành các MobX observable theo từng property, vì vậy cập nhật 50 issue sẽ được xử lý thành 50 lần rerender ở cấp ô thay vì rerender cả danh sách
- Mô hình nhập liệu ưu tiên bàn phím, với shortcut và command palette toàn cục cho mọi tác vụ phổ biến
- Hoạt ảnh chỉ dùng các thuộc tính thân thiện với GPU, và không animate các layout-triggering property
- Phần khó không nằm ở bản thân việc triển khai, mà ở việc duy trì sự tập trung vào chất lượng chi tiết trong nhiều năm khi codebase trưởng thành, mở rộng và gặp các ràng buộc mới
2 bình luận
Linear thì tốt đấy, nhưng các công ty cũ phải duy trì dữ liệu hoặc quy trình hiện có nên vẫn ở lại với Jira.
Ý kiến trên Hacker News
Nếu muốn đưa trải nghiệm như thế này vào ứng dụng, bạn nên xem Zero(https://zero.rocicorp.dev/)
Demo trực tiếp: https://gigabugs.rocicorp.dev/
Danh sách các lựa chọn thay thế cũng có ở đây: https://zero.rocicorp.dev/docs/when-to-use#alternatives
Nếu tò mò về cách nó hoạt động bên trong, tài liệu thiết kế của Replicache cũng đáng tham khảo: https://doc.replicache.dev/concepts/how-it-works
Replicache là tiền thân của Zero, và giao thức cốt lõi vẫn hoạt động theo cách tương tự
Không chỉ có lợi thế hiệu năng rõ rệt từ việc đồng bộ dữ liệu xuống client, mà tôi còn ngạc nhiên vì code React trở nên đơn giản hơn rất nhiều. Khi có một engine đồng bộ, phần lớn state phía client biến mất và bạn có thể tư duy hầu hết code component theo cách đồng bộ
Đây có lẽ là lựa chọn gần nhất với thứ bạn muốn mà không cần lập hẳn một đội chuyên trách
Tôi luôn nghe nói Linear rất nhanh, nhưng sau khi thực sự dùng hằng ngày thì sự hào hứng của tôi giảm hẳn. Tìm kiếm khá chậm, UI thường ì ạch, và dù nhìn đẹp, “Pulse” giống như một trận lũ nhiễu loạn ngay cả ở quy mô nhỏ
Rất khó tìm được thứ mình cần nên cuối cùng tôi phải đưa mọi thứ vào mục yêu thích. Trello thời kỳ đầu vượt trội tuyệt đối về trải nghiệm theo dõi dự án
Năm ngoái có người reverse engineer engine đồng bộ của Linear rồi đưa lên GitHub kèm theo phần giải thích rất hay
https://github.com/wzhudev/reverse-linear-sync-engine/blob/m...
Những web app đồng bộ local-first kiểu này thực sự rất thú vị và có thể rất hữu ích, nhưng tôi nghĩ tiền đề ở đây hơi sai
Đó là kiểu tiền đề như “trong Linear chỉ mất vài mili giây để cập nhật một issue. Một app CRUD truyền thống mất khoảng 300ms cho cùng thao tác”, hay “mọi dữ liệu qua lại giữa client và server đều phải trả giá bằng hàng trăm mili giây”
Dù bạn không thể giải quyết chuyện thời gian khứ hồi giữa HTTP client và server bị kéo dài bởi tốc độ ánh sáng, bạn vẫn có thể đặt backend gần người dùng và làm cho nó đủ nhanh
Ví dụ, hoàn toàn có thể vận hành một backend web app với thời gian khứ hồi khoảng 10ms đối với đa số người dùng, và để backend render phản hồi cũng trong khoảng 10ms. Nghĩa là một app CRUD truyền thống cũng có thể thực hiện cùng thao tác đó trong khoảng 30ms chứ không phải 300ms
Có thể Linear mất nhiều thời gian hơn ở backend vì những lý do chính đáng và cần frontend hỗ trợ, nhưng không thể khái quát hóa điều đó. Mỗi mẩu JavaScript cũng có cái giá riêng của nó
us-west-1 là 60ms, eu-centra-1 là 100ms, còn châu Á là 200ms. Mà đây còn là lưu lượng giữa các data center; độ trễ thực tế từ Internet công cộng tới đường truyền gia đình còn tệ hơn nhiều
Cơ sở dữ liệu phải nằm chính xác ở một region. Dù đặt ở đâu thì phần lớn người dùng trên thế giới cũng sẽ cách đó hơn 100ms
Lý do endpoint ở đâu không quan trọng là vì để đọc và ghi dữ liệu, endpoint vẫn phải nói chuyện với cơ sở dữ liệu. Ngay khi bạn định sao chép dữ liệu lại gần người dùng, rốt cuộc bạn sẽ sở hữu một cơ sở dữ liệu đồng bộ local-first
Dù tự xây hay dùng giải pháp có sẵn, cơ sở dữ liệu sao chép đó cũng sẽ có toàn bộ những vấn đề giống đồng bộ phía client, và vẫn còn độ trễ mạng đáng kể. Vật lý không thể bị lách qua, nên bạn chỉ có thể chọn hoặc là commit trong 0,25 giây cho đa số người dùng, hoặc là tính nhất quán cuối cùng, tức là đồng bộ hóa
Tất nhiên bạn có thể đặt một “backend trung gian” trên mạng edge CDN toàn cầu, nhưng đến thời điểm đó bạn sẽ phải trả cùng mức chi phí phức tạp như cách đặt “backend trung gian” này trên client
Trong trường hợp xấu nhất, worker nền có thể phát ra thông báo cập nhật thất bại và UI thread chỉ việc nhận rồi hiển thị. Đường đi thành công vẫn nhanh như chớp
Việc xây dựng cơ sở dữ liệu nhất quán cuối cùng rất khó, và tuy có thể chấp nhận được với trường hợp sử dụng của Linear, nhưng việc không biết bản cập nhật của mình đã tới máy chủ, tức là tới đội, hay chưa là một vấn đề
Ở những dự án khác tôi từng tham gia trước đây, độ trễ đồng bộ đã tạo ra vô số vấn đề, nên tôi luôn chọn giải pháp đồng bộ. Tôi chỉ bật các tính năng hào nhoáng khi thật sự cần, và thà tối ưu máy chủ cực nhanh rồi để người dùng chịu độ trễ mạng còn hơn
Công ty tôi đang dùng Linear. Tôi biết mình thuộc phe thiểu số, nhưng trải nghiệm người dùng thực sự rất khó chịu. Cũng khó mà gọi là nhanh
Bản thân trang về mặt kỹ thuật thì tải cũng tương đối nhanh, nhưng khoảng một nửa thời gian bạn chỉ thấy các con số trên trang tự đổi mà không có bất kỳ chỉ báo trực quan nào cho thấy dữ liệu vẫn đang tải
Tệ đến mức trên Linear tôi chỉ tạo issue với một câu mô tả ngắn, rồi sang GitHub để điền nội dung chi tiết. Linear chỉ giỏi và nhanh đến thế thôi
Đáng tiếc là để công ty sống sót và tiến lên phân khúc thị trường cao hơn thì thực tế gần như không còn con đường nào khác
Tôi sẽ không dùng từ “nhanh”. Việc giảm thời gian cập nhật issue từ 300ms xuống còn “vài” mili giây không quan trọng lắm khi bản thân việc tải ban đầu đã mất 30 giây
Nó vẫn tốt hơn Jira, nhưng đó là một tiêu chuẩn rất thấp
Hay đấy. Có lẽ tôi cũng có thể đưa thứ tương tự vào game trình duyệt và engine mình đang phát triển để loại bỏ hoàn toàn trạng thái tải sau lần tải đầu tiên. Của tôi là một cấu trúc asset tĩnh hoàn toàn phía client, không có máy chủ
Tôi đã bị ám ảnh với hiệu năng của game này. Trước cuối tuần trước, tôi còn chật vật để giữ 120fps trên M1 MacBook Pro khi mô phỏng 128 người chơi đồng thời, xử lý tìm đường, logic chiến thuật nặng và render tất cả trong viewport, thỉnh thoảng vẫn rớt khung hình với thời gian khung hình khoảng 4ms
Trong cuối tuần tôi đã làm việc hiệu năng rất quyết liệt, và giờ có thể mô phỏng 2048 người chơi đồng thời với thời gian khung hình dưới một mili giây. Con số này đã bao gồm render, toàn bộ logic và cả sinh thủ tục
Tôi cũng áp dụng throttling CPU 11,2 lần để mô phỏng thiết bị di động cấu hình thấp, và ngay cả khi đó vẫn đạt 60fps ổn định với khoảng 5ms thời gian khung hình ở mức 256~512 người chơi đồng thời. Nút thắt chính hiện tại là một vài vấn đề logic và thời gian khởi động/boot cần cải thiện trên thiết bị yếu, nên có lẽ tôi có thể học được điều gì đó từ Linear
Tôi thực ra thấy Linear khá chậm. Có những tuần chỉ cần mở tab đó một lúc là CPU chạy 100%
Cũng thú vị đấy. Thành thật mà nói tôi chưa bao giờ nghĩ Linear là “nhanh”. Nó vẫn có độ trễ như hầu hết ứng dụng web khác, nhưng so với JIRA thì đúng là nhanh như ánh sáng
Bản thân Linear rất tuyệt, và sau tra tấn JIRA thì đúng là một luồng gió mới. Nếu muốn nói về route lạc quan và sự “nhanh”, có lẽ nên bắt đầu từ Gmail trước chăng
Câu trả lời cho tốc độ là tải trước. Về cơ bản là tải xuống cơ sở dữ liệu phía client ngay từ lúc khởi tạo và đặt một chiến lược vô hiệu hóa bộ nhớ đệm
Tôi đã tạo starfx để xử lý khía cạnh đồng bộ dữ liệu của mô hình này: https://starfx.bower.sh/learn#data-loading-strategy-stale-wh...